From 17873240b113eead6bd83c3c8a54f047a61c2877 Mon Sep 17 00:00:00 2001 From: Mike Nicholaides Date: Thu, 26 May 2011 15:05:16 -0400 Subject: [PATCH] Adding ability to render webpage to a PNG The driver has a #render method which takes a destination file path and an options hash for setting the dimensions of the browser's viewport --- .gitignore | 3 + Gemfile | 2 +- Gemfile.lock | 4 ++ lib/capybara/driver/webkit.rb | 8 +++ lib/capybara/driver/webkit/browser.rb | 4 ++ spec/driver_rendering_spec.rb | 79 +++++++++++++++++++++++++++ src/Connection.cpp | 1 + src/Render.cpp | 19 +++++++ src/Render.h | 12 ++++ src/WebPage.cpp | 32 +++++++++++ src/WebPage.h | 1 + src/find_command.h | 3 +- src/webkit_server.pro | 4 +- 13 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 spec/driver_rendering_spec.rb create mode 100644 src/Render.cpp create mode 100644 src/Render.h diff --git a/.gitignore b/.gitignore index 1ea2664..c7bb96b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ moc_*.cpp .bundle pkg src/webkit_server +.DS_Store +tmp +.rvmrc diff --git a/Gemfile b/Gemfile index ed17047..7020516 100644 --- a/Gemfile +++ b/Gemfile @@ -2,4 +2,4 @@ source "http://rubygems.org" gem "rspec", :require => false gem "capybara" gem "sinatra", :require => false - +gem "mini_magick", :require => false diff --git a/Gemfile.lock b/Gemfile.lock index 7a3085e..b52c5d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -19,6 +19,8 @@ GEM rake (>= 0.8.7) json_pure (1.5.1) mime-types (1.16) + mini_magick (3.2.1) + subexec (~> 0.0.4) nokogiri (1.4.4) rack (1.2.1) rack-test (0.5.7) @@ -41,6 +43,7 @@ GEM sinatra (1.1.2) rack (~> 1.1) tilt (~> 1.2) + subexec (0.0.4) tilt (1.2.2) xpath (0.1.3) nokogiri (~> 1.3) @@ -50,5 +53,6 @@ PLATFORMS DEPENDENCIES capybara + mini_magick rspec sinatra diff --git a/lib/capybara/driver/webkit.rb b/lib/capybara/driver/webkit.rb index 188ef4c..8a06fb4 100644 --- a/lib/capybara/driver/webkit.rb +++ b/lib/capybara/driver/webkit.rb @@ -84,6 +84,14 @@ class Capybara::Driver::Webkit false end + def render(path, options={}) + options[:width] ||= 1000 + options[:height] ||= 10 + + browser.render path, options[:width], options[:height] + end + + private def url(path) diff --git a/lib/capybara/driver/webkit/browser.rb b/lib/capybara/driver/webkit/browser.rb index 2c533b7..0226994 100644 --- a/lib/capybara/driver/webkit/browser.rb +++ b/lib/capybara/driver/webkit/browser.rb @@ -64,6 +64,10 @@ class Capybara::Driver::Webkit command('Execute', script) end + def render(path, width, height) + command "Render", path, width, height + end + private def start_server diff --git a/spec/driver_rendering_spec.rb b/spec/driver_rendering_spec.rb new file mode 100644 index 0000000..845c1c1 --- /dev/null +++ b/spec/driver_rendering_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' +require 'capybara/driver/webkit' +require 'mini_magick' + +describe Capybara::Driver::Webkit, "rendering an image" do + + before(:all) do + # Set up the tmp directory and file name + tmp_dir = File.join(PROJECT_ROOT, 'tmp') + FileUtils.mkdir_p tmp_dir + @file_name = File.join(tmp_dir, 'render-test.png') + + app = lambda do |env| + body = <<-HTML + + +

Hello World

+ + + HTML + [200, + { 'Content-Type' => 'text/html', 'Content-Length' => body.length.to_s }, + [body]] + end + + @driver = Capybara::Driver::Webkit.new(app, :browser => $webkit_browser) + @driver.visit("/hello/world?success=true") + end + + after(:all) { @driver.reset! } + + def render(options) + FileUtils.rm_f @file_name + @driver.render @file_name, options + + @image = MiniMagick::Image.open @file_name + end + + context "with default options" do + before(:all){ render({}) } + + it "should be a PNG" do + @image[:format].should == "PNG" + end + + it "width default to 1000px (with 15px less for the scrollbar)" do + @image[:width].should == 1000-15 + end + + it "height should be at least 10px" do + @image[:height].should >= 10 + end + end + + context "with dimensions set larger than necessary" do + before(:all){ render(:width => 500, :height => 400) } + + it "width should match the width given" do + @image[:width].should == 500 + end + + it "height should match the height given" do + @image[:height].should > 10 + end + end + + context "with dimensions set smaller than the document's default" do + before(:all){ render(:width => 50, :height => 10) } + + it "width should be greater than the width given" do + @image[:width].should > 50 + end + + it "height should be greater than the height given" do + @image[:height].should > 10 + end + end + +end diff --git a/src/Connection.cpp b/src/Connection.cpp index 56d82d4..488eaaa 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -11,6 +11,7 @@ #include "Execute.h" #include "FrameFocus.h" #include "Header.h" +#include "Render.h" #include #include diff --git a/src/Render.cpp b/src/Render.cpp new file mode 100644 index 0000000..4c25a97 --- /dev/null +++ b/src/Render.cpp @@ -0,0 +1,19 @@ +#include "Render.h" +#include "WebPage.h" + +Render::Render(WebPage *page, QObject *parent) : Command(page, parent) { +} + +void Render::start(QStringList &arguments) { + QStringList functionArguments(arguments); + QString imagePath = functionArguments.takeFirst(); + int width = functionArguments.takeFirst().toInt(); + int height = functionArguments.takeFirst().toInt(); + + QSize size(width, height); + page()->setViewportSize(size); + + bool result = page()->render( imagePath ); + + emit finished(new Response(result)); +} diff --git a/src/Render.h b/src/Render.h new file mode 100644 index 0000000..b433d4d --- /dev/null +++ b/src/Render.h @@ -0,0 +1,12 @@ +#include "Command.h" +#include + +class WebPage; + +class Render : public Command { + Q_OBJECT + + public: + Render(WebPage *page, QObject *parent = 0); + virtual void start(QStringList &arguments); +}; diff --git a/src/WebPage.cpp b/src/WebPage.cpp index 7bfe7d9..5492003 100644 --- a/src/WebPage.cpp +++ b/src/WebPage.cpp @@ -106,3 +106,35 @@ QString WebPage::failureString() { return QString("Unable to load URL: ") + currentFrame()->requestedUrl().toString(); } +/* + * Swiped from Phantom.js, but removed code for rendering to PDFs and GIFs + * + * Takes a QString of the file path to save the image to + * Returns true if it was able to save the file + */ +bool WebPage::render(const QString &fileName) { + QFileInfo fileInfo(fileName); + QDir dir; + dir.mkpath(fileInfo.absolutePath()); + + QSize viewportSize = this->viewportSize(); + QSize pageSize = this->mainFrame()->contentsSize(); + if (pageSize.isEmpty()) { + return false; + } + + QImage buffer(pageSize, QImage::Format_ARGB32); + buffer.fill(qRgba(255, 255, 255, 0)); + + QPainter p(&buffer); + p.setRenderHint( QPainter::Antialiasing, true); + p.setRenderHint( QPainter::TextAntialiasing, true); + p.setRenderHint( QPainter::SmoothPixmapTransform, true); + + this->setViewportSize(pageSize); + this->mainFrame()->render(&p); + p.end(); + this->setViewportSize(viewportSize); + + return buffer.save(fileName); +} diff --git a/src/WebPage.h b/src/WebPage.h index e6665bb..45f55db 100644 --- a/src/WebPage.h +++ b/src/WebPage.h @@ -10,6 +10,7 @@ class WebPage : public QWebPage { QString failureString(); QString userAgentForUrl(const QUrl &url ) const; void setUserAgent(QString userAgent); + bool render(const QString &fileName); public slots: bool shouldInterruptJavaScript(); diff --git a/src/find_command.h b/src/find_command.h index 04e55bb..fa6a9a8 100644 --- a/src/find_command.h +++ b/src/find_command.h @@ -12,4 +12,5 @@ CHECK_COMMAND(Source) CHECK_COMMAND(Evaluate) CHECK_COMMAND(Execute) CHECK_COMMAND(FrameFocus) -CHECK_COMMAND(Header) \ No newline at end of file +CHECK_COMMAND(Header) +CHECK_COMMAND(Render) diff --git a/src/webkit_server.pro b/src/webkit_server.pro index e22f2b4..7a20b92 100644 --- a/src/webkit_server.pro +++ b/src/webkit_server.pro @@ -1,8 +1,8 @@ TEMPLATE = app TARGET = webkit_server DESTDIR = . -HEADERS = WebPage.h Server.h Connection.h Command.h Visit.h Find.h Reset.h Node.h JavascriptInvocation.h Url.h Source.h Evaluate.h Execute.h FrameFocus.h Response.h NetworkAccessManager.h Header.h -SOURCES = main.cpp WebPage.cpp Server.cpp Connection.cpp Command.cpp Visit.cpp Find.cpp Reset.cpp Node.cpp JavascriptInvocation.cpp Url.cpp Source.cpp Evaluate.cpp Execute.cpp FrameFocus.cpp Response.cpp NetworkAccessManager.cpp Header.cpp +HEADERS = WebPage.h Server.h Connection.h Command.h Visit.h Find.h Reset.h Node.h JavascriptInvocation.h Url.h Source.h Evaluate.h Execute.h FrameFocus.h Response.h NetworkAccessManager.h Header.h Render.h +SOURCES = main.cpp WebPage.cpp Server.cpp Connection.cpp Command.cpp Visit.cpp Find.cpp Reset.cpp Node.cpp JavascriptInvocation.cpp Url.cpp Source.cpp Evaluate.cpp Execute.cpp FrameFocus.cpp Response.cpp NetworkAccessManager.cpp Header.cpp Render.cpp RESOURCES = webkit_server.qrc QT += network webkit CONFIG += console