webshooter 0.0.1a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.png
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # A sample Gemfile
2
+ source "http://rubygems.org"
3
+
4
+ gem "webshooter", :path => "."
data/Gemfile.lock ADDED
@@ -0,0 +1,14 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ webshooter (0.0.1a)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+
10
+ PLATFORMS
11
+ ruby
12
+
13
+ DEPENDENCIES
14
+ webshooter!
data/README.txt ADDED
@@ -0,0 +1,32 @@
1
+ This library uses the Webkit Ruby Cocoa library to take headless webshots
2
+
3
+ # Usage
4
+
5
+ ## From within ruby
6
+ require 'webshooter'
7
+ Webshooter.capture('http://www.jedi.be','jedi.png','1024x768')
8
+
9
+ ## As a commandline tool
10
+ webshooter 'http://www.jedi.be' 'jedi.png' '1024x768'
11
+
12
+ # Limitations
13
+ - does not handle redirects currently
14
+ - create more configurable options
15
+ - cleanup code
16
+ - only support png
17
+
18
+ # Inspiration
19
+
20
+ This library is a compilation of various parts I found on the web
21
+ The research was done a few years ago, so unfortunatly I don't have all the references anymore
22
+
23
+ - Darkroom - Copyright (c) 2007 Justin Palmer.
24
+ - https://gist.github.com/34824
25
+ - https://gist.github.com/86435
26
+
27
+ - Thanks to the heavy lifting done by others:
28
+ - https://gist.github.com/244948
29
+ - http://pastie.caboo.se/69235
30
+ - http://pastie.caboo.se/68511
31
+ - https://gist.github.com/248077 Webview to PDF
32
+ - http://www.bencurtis.com/wp-content/uploads/2008/05/snapper.rb
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'bundler/setup'
4
+ Bundler::GemHelper.install_tasks
5
+
data/bin/webshooter.rb ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+
4
+ require 'webshotr'
5
+
6
+ module Webshotr
7
+ class CLI
8
+ #Ruby CLI tool - http://rubylearning.com/blog/2011/01/03/how-do-i-make-a-command-line-tool-in-ruby/
9
+ def self.execute(args)
10
+ url=args[0]
11
+ filename=args[1]
12
+ size=args[2]
13
+ webshotr=Webshotr.capture(url, filename, size )
14
+ end
15
+ end
16
+ end
17
+
18
+ Webshotr::CLI.execute(ARGV)
@@ -0,0 +1,9 @@
1
+ module Webshooter
2
+ # We want to allow any https certificate
3
+ # OSX::NSURLRequest.setAllowsAnyHTTPSCertificate(true,myURI.host) has method missing
4
+ # Therefore we create a wrapper object
5
+ # NewRequest.setAllowsAnyHTTPSCertificate_forHost(true,OSX::NSURL.URLWithString(uri))
6
+
7
+ class NewNSURLRequest < OSX::NSURLRequest
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Webshooter
2
+ VERSION = "0.0.1a"
3
+ end
@@ -0,0 +1,224 @@
1
+ require 'osx/cocoa'
2
+ require 'logger'
3
+ require 'uri'
4
+
5
+ require 'webshooter/newnsurlrequest'
6
+ OSX.require_framework 'WebKit'
7
+
8
+ #Respond to URL not found....
9
+ #http://stackoverflow.com/questions/697108/testing-use-of-nsurlconnection-with-http-response-error-statuses
10
+ #http://www.gigliwood.com/weblog/Cocoa/Q__When_is_an_conne.html
11
+ #http://stackoverflow.com/questions/2236407/how-to-detect-and-handle-http-error-codes-in-uiwebview
12
+
13
+
14
+ module Webshooter
15
+
16
+ class WebShotProcessor
17
+
18
+ def initialize
19
+ @logger = Logger.new(STDOUT)
20
+
21
+ # get NSApplication code ready for action
22
+ OSX.NSApplicationLoad
23
+
24
+ # Make an offscreen window
25
+ # The frame is in screen coordinates, but it's irrelevant since we'll never display it
26
+ # We want a buffered backing store, simply so we can read it later
27
+ # We don't want to defer, or it won't draw until it gets on the screen
28
+ rect = OSX::NSRect.new(-16000,-16000,100,100)
29
+ @window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
30
+ rect , OSX::NSBorderlessWindowMask, OSX::NSBackingStoreBuffered, false
31
+ )
32
+
33
+ # Make a web @webView - the frame here is in window coordinates
34
+ @webView = OSX::WebView.alloc.initWithFrame(rect)
35
+
36
+ # Use the screen stylesheet, rather than the print one.
37
+ @webView.setMediaStyle('screen')
38
+ # Make sure we don't save any of the prefs that we change.
39
+ @webView.preferences.setAutosaves(false)
40
+ # Set some useful options.
41
+ @webView.preferences.setShouldPrintBackgrounds(true)
42
+ @webView.preferences.setJavaScriptCanOpenWindowsAutomatically(false)
43
+ @webView.preferences.setAllowsAnimatedImages(false)
44
+ # Make sure we don't get a scroll bar.
45
+ @webView.mainFrame.frameView.setAllowsScrolling(false)
46
+
47
+ # This @delegate will get a message when the load completes
48
+ @delegate = SimpleLoadDelegate.alloc.init
49
+ @delegate.webshooter = self
50
+ @webView.setFrameLoadDelegate(@delegate)
51
+
52
+ # Replace the window's content @webView with the web @webView
53
+ @window.setContentView(@webView)
54
+ @webView.release
55
+ puts "its not in the release"
56
+ end
57
+
58
+ def capture(uri, path, dimensions = "1024x768" )
59
+
60
+ snapshot_dimension=dimensions.split('x')
61
+ # Tell the frame to load the URL we want
62
+ @webView.window.setContentSize(snapshot_dimension)
63
+ @webView.setFrameSize(snapshot_dimension)
64
+
65
+ myURI = URI.parse(uri)
66
+
67
+ #Allow all https certificates
68
+ NewNSURLRequest.setAllowsAnyHTTPSCertificate_forHost(true,myURI.host)
69
+
70
+ #puts "Getting ready for the loadRequest"+uri
71
+ @webView.mainFrame.loadRequest(NewNSURLRequest.requestWithURL(OSX::NSURL.URLWithString(uri)))
72
+
73
+ #
74
+ # Run the main event loop until the frame loads
75
+ @timeout=false
76
+ result=OSX.CFRunLoopRunInMode(OSX::KCFRunLoopDefaultMode, 20, false)
77
+ if (result == OSX::KCFRunLoopRunTimedOut)
78
+ @timeout=true
79
+ end
80
+
81
+ #This is what we need but the upon_ also changes
82
+ #OSX.CFRunLoopRun
83
+ #puts "first loop enters here"
84
+
85
+ puts "its not in the after upon success"
86
+
87
+ upon_success do |view| # Capture the content of the view as an image
88
+
89
+ view.window.orderFront(nil)
90
+ view.window.display
91
+
92
+ #puts "-------------------------------------------------"
93
+ #puts "We got success"
94
+ @docview=view.mainFrame.frameView.documentView
95
+
96
+ #We need the contents of the first frame
97
+ #pp view.bounds
98
+ #pp @docview.bounds
99
+
100
+ if @docview.bounds.size.height == 0.0
101
+ #pp "trying alternative"
102
+ #pp @docview
103
+ @docview= view.mainFrame.frameView.documentView
104
+ else
105
+ view.window.setContentSize(@docview.bounds.size)
106
+ view.setFrame(@docview.bounds)
107
+ if view.bounds.size.height > 300000
108
+ #view.bounds.size.height=300000
109
+ #puts "Adjusted maximum size to 300000"
110
+ end
111
+ end
112
+
113
+
114
+ # Write the bitmap to a file as a PNG
115
+ #
116
+ #view.bounds=initialsize
117
+ #view.window.setContentSize(view.bounds.size)
118
+
119
+ view.setNeedsDisplay(true)
120
+ view.displayIfNeeded
121
+ if view.bounds.size.height < 300000
122
+ view.lockFocus
123
+ bitmap = OSX::NSBitmapImageRep.alloc.initWithFocusedViewRect(view.bounds)
124
+ bitmap.representationUsingType_properties(OSX::NSPNGFileType, nil).writeToFile_atomically(path, true)
125
+ bitmap.release
126
+ view.unlockFocus
127
+
128
+ else
129
+ puts "Something went wrong, the size became to big"
130
+ end
131
+
132
+ end
133
+
134
+ upon_failure do |error, logger|
135
+ logger.warn("Unable to load URI: #{uri} (#{error})")
136
+ end
137
+
138
+
139
+ end
140
+
141
+ def release
142
+ #@window.release
143
+ @delegate.release
144
+ end
145
+
146
+ attr_accessor :load_success, :load_error
147
+
148
+ private
149
+
150
+ def upon_success(&block)
151
+ block.call(@webView) if @load_success
152
+ end
153
+
154
+ def upon_failure(&block)
155
+ if (@timeout)
156
+ block.call("Timeout error", @logger) unless @load_success
157
+ else
158
+ block.call(@load_error.localizedDescription, @logger) unless @load_success
159
+ end
160
+ end
161
+
162
+
163
+ class SimpleLoadDelegate < OSX::NSObject
164
+
165
+ attr_accessor :webshooter
166
+
167
+ def stopLoop
168
+ mainLoop=OSX.CFRunLoopGetMain
169
+ currentLoop=OSX.CFRunLoopGetCurrent
170
+ OSX.CFRunLoopStop(mainLoop)
171
+ OSX.CFRunLoopStop(currentLoop)
172
+ end
173
+
174
+ def webView_didFinishLoadForFrame(sender, frame)
175
+ #This did the trick, we have to wait for the right frame to load, not other frames
176
+ if (frame == sender.mainFrame)
177
+ then
178
+ #puts "Finish Load For Frame"
179
+ @webshooter.load_success = true;
180
+
181
+ stopLoop
182
+ else
183
+ #puts "WARN: the mainframe is not the frame"
184
+ return
185
+ end
186
+ end
187
+
188
+ def webView_didFailLoadWithError_forFrame(webview, load_error, frame)
189
+
190
+ #This is trick # 2
191
+ #We have to catch this stupid error
192
+ if (load_error.code == OSX::NSURLErrorCancelled)
193
+ then
194
+ #pp "WARN: did Fail with Error For Frame"
195
+ #pp "WARN: we don't give up"
196
+ return
197
+ else
198
+ #puts load_error.localizedDescription
199
+ end
200
+
201
+ @webshooter.load_success = false; @webshooter.load_error = load_error;
202
+
203
+ stopLoop
204
+ end
205
+
206
+ def webView_didFailProvisionalLoadWithError_forFrame(webview, load_error, frame)
207
+ if (load_error.code == OSX::NSURLErrorCancelled)
208
+ then
209
+ #pp "WARN: did Fail PROVISIONAL LOAD WITH ERROR For Frame"
210
+ #pp "WARN: we don't give up"
211
+ return
212
+ else
213
+ #puts load_error.localizedDescription
214
+ end
215
+ @webshooter.load_success = false; @webshooter.load_error = load_error;
216
+
217
+ stopLoop
218
+ end
219
+
220
+ end
221
+
222
+ end
223
+
224
+ end
data/lib/webshooter.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'webshooter/webshotprocessor'
2
+
3
+ # We might handle redirection in the future
4
+ # http://shadow-file.blogspot.com/2009/03/handling-http-redirection-in-ruby.html
5
+
6
+ module Webshooter
7
+ class Webshooter
8
+ def self.capture(uri, path, dimensions = "1024x768" )
9
+ webProcessor=WebShotProcessor.new
10
+ webProcessor.capture(uri,path,dimensions)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # DarkRoom
4
+ # Takes fullsize screenshots of a web page.
5
+ # Copyright (c) 2007 Justin Palmer.
6
+ #
7
+ # Released under an MIT LICENSE
8
+ #
9
+ # Usage
10
+ # ====
11
+ # ruby ./darkroom.rb http://activereload.net
12
+ # ruby ./darkroom.rb --output=google.png http://google.com
13
+ #
14
+ require 'optparse'
15
+ require 'osx/cocoa'
16
+ OSX.require_framework 'Webkit'
17
+
18
+ module ActiveReload
19
+ module DarkRoom
20
+ USER_AGENT = "DarkRoom/0.1"
21
+ class Photographer
22
+ def initialize
23
+ options = {}
24
+ opts = OptionParser.new do |opts|
25
+ opts.banner = "Usage: #$0 [options] URL"
26
+
27
+ opts.on('-w', '--width=[WIDTH]', Integer, 'Force width of the screenshot') do |v|
28
+ options[:width] = v
29
+ end
30
+
31
+ opts.on('-h', '--height=[HEIGHT]', Integer, 'Force height of screenshot') do |v|
32
+ options[:height] = v
33
+ end
34
+
35
+ opts.on('-o', '--output=[FILENAME]', String, 'Specify filename for saving') do |v|
36
+ options[:output] = v
37
+ end
38
+
39
+ opts.on_tail('-h', '--help', 'Display this message and exit') do
40
+ puts opts
41
+ exit
42
+ end
43
+ end.parse!
44
+ options[:width] ||= 1024
45
+ options[:height] ||= 0
46
+ options[:website] = ARGV.first || 'http://google.com'
47
+ Camera.shoot(options)
48
+ Camera.shoot(options)
49
+ end
50
+ end
51
+
52
+ class Camera
53
+ def self.shoot(options)
54
+ app = OSX::NSApplication.sharedApplication
55
+ delegate = Processor.alloc.init
56
+ delegate.options = options
57
+ app.setDelegate(delegate)
58
+ app.run
59
+ end
60
+ end
61
+
62
+ class Processor < OSX::NSObject
63
+ include OSX
64
+ attr_accessor :options, :web_view
65
+
66
+ def initialize
67
+ rect = [-16000.0, -16000.0, 100, 100]
68
+ win = NSWindow.alloc.initWithContentRect_styleMask_backing_defer(rect, NSBorderlessWindowMask, 2, 0)
69
+
70
+ @web_view = WebView.alloc.initWithFrame(rect)
71
+ @web_view.mainFrame.frameView.setAllowsScrolling(false)
72
+ @web_view.setApplicationNameForUserAgent(USER_AGENT)
73
+
74
+ NSNotificationCenter.defaultCenter.objc_send(:addObserver, self,
75
+ :selector, :webview_progress_finished,
76
+ :name, WebViewProgressFinishedNotification,
77
+ :object, @web_view)
78
+
79
+ win.setContentView(@web_view)
80
+ end
81
+
82
+ def webview_progress_finished(sender)
83
+ viewport = web_view.mainFrame.frameView.documentView
84
+ viewport.window.orderFront(nil)
85
+ viewport.window.display
86
+ viewport.window.setContentSize([@options[:width], (@options[:height] > 0 ? @options[:height] : viewport.bounds.height)])
87
+ viewport.setFrame(viewport.bounds)
88
+ sleep(@options[:delay]) if @options[:delay]
89
+ capture_and_save(viewport)
90
+ end
91
+
92
+ def applicationDidFinishLaunching(notification)
93
+ @options[:output] ||= "#{Time.now.strftime('%m-%d-%y-%H%I%S')}.png"
94
+ @web_view.window.setContentSize([@options[:width], @options[:height]])
95
+ @web_view.setFrameSize([@options[:width], @options[:height]])
96
+ @web_view.mainFrame.loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(@options[:website])))
97
+ end
98
+
99
+ def capture_and_save(view)
100
+ print "we never get here"
101
+ view.lockFocus
102
+ bitmap = NSBitmapImageRep.alloc.initWithFocusedViewRect(view.bounds)
103
+ view.unlockFocus
104
+
105
+ bitmap.representationUsingType_properties(NSPNGFileType, nil).writeToFile_atomically(@options[:output], true)
106
+ #NSApplication.sharedApplication.terminate(nil)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ ActiveReload::DarkRoom::Photographer.new
112
+ ActiveReload::DarkRoom::Photographer.new
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # DarkRoom
4
+ # Takes fullsize screenshots of a web page.
5
+ # Copyright (c) 2007 Justin Palmer.
6
+ #
7
+ # Released under an MIT LICENSE
8
+ #
9
+ # Usage
10
+ # ====
11
+ # ruby ./darkroom.rb http://activereload.net
12
+ # ruby ./darkroom.rb --output=google.png http://google.com
13
+ #
14
+ require 'optparse'
15
+ require 'osx/cocoa'
16
+ OSX.require_framework 'Webkit'
17
+
18
+ module ActiveReload
19
+ module DarkRoom
20
+ USER_AGENT = "DarkRoom/0.1"
21
+
22
+ class Camera
23
+ def self.shoot(options)
24
+ app = OSX::NSApplication.sharedApplication
25
+ delegate = Processor.alloc.init
26
+ delegate.options = options
27
+ app.setDelegate(delegate)
28
+ app.run
29
+ end
30
+ end
31
+
32
+ class Processor < OSX::NSObject
33
+ include OSX
34
+ attr_accessor :options, :web_view
35
+
36
+ def initialize
37
+ rect = [-16000.0, -16000.0, 100, 100]
38
+ win = NSWindow.alloc.initWithContentRect_styleMask_backing_defer(rect, NSBorderlessWindowMask, 2, 0)
39
+
40
+ @web_view = WebView.alloc.initWithFrame(rect)
41
+ @web_view.mainFrame.frameView.setAllowsScrolling(false)
42
+ @web_view.setApplicationNameForUserAgent(USER_AGENT)
43
+
44
+ NSNotificationCenter.defaultCenter.objc_send(:addObserver, self,
45
+ :selector, :webview_progress_finished,
46
+ :name, WebViewProgressFinishedNotification,
47
+ :object, @web_view)
48
+
49
+ win.setContentView(@web_view)
50
+ end
51
+
52
+ def webview_progress_finished(sender)
53
+ viewport = web_view.mainFrame.frameView.documentView
54
+ viewport.window.orderFront(nil)
55
+ viewport.window.display
56
+ viewport.window.setContentSize([@options[:width], (@options[:height] > 0 ? @options[:height] : viewport.bounds.height)])
57
+ viewport.setFrame(viewport.bounds)
58
+ sleep(@options[:delay]) if @options[:delay]
59
+ capture_and_save(viewport)
60
+ end
61
+
62
+ def applicationDidFinishLaunching(notification)
63
+ @options[:output] ||= "#{Time.now.strftime('%m-%d-%y-%H%I%S')}.png"
64
+ @web_view.window.setContentSize([@options[:width], @options[:height]])
65
+ @web_view.setFrameSize([@options[:width], @options[:height]])
66
+ @web_view.mainFrame.loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(@options[:website])))
67
+ end
68
+
69
+ def capture_and_save(view)
70
+ print "we never get here"
71
+ view.lockFocus
72
+ bitmap = NSBitmapImageRep.alloc.initWithFocusedViewRect(view.bounds)
73
+ view.unlockFocus
74
+
75
+ bitmap.representationUsingType_properties(NSPNGFileType, nil).writeToFile_atomically(@options[:output], true)
76
+ #NSApplication.sharedApplication.terminate(nil)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ #ActiveReload::DarkRoom::Photographer.new
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Thanks to the heavy lifting done by others:
4
+ # http://pastie.caboo.se/69235
5
+ # http://pastie.caboo.se/68511
6
+
7
+ require 'osx/cocoa'
8
+ OSX.require_framework 'WebKit'
9
+
10
+ class Snapper < OSX::NSObject
11
+
12
+ def init
13
+ # This sets up some context that we need for creating windows.
14
+ OSX::NSApplication.sharedApplication
15
+
16
+ # Create an offscreen window into which we can stick our WebView.
17
+ @window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
18
+ [0, 0, 800, 600], OSX::NSBorderlessWindowMask, OSX::NSBackingStoreBuffered, false
19
+ )
20
+
21
+ # Create a WebView and stick it in our offscreen window.
22
+ @webView = OSX::WebView.alloc.initWithFrame([0, 0, 800, 600])
23
+ @window.setContentView(@webView)
24
+
25
+ # Use the screen stylesheet, rather than the print one.
26
+ @webView.setMediaStyle('screen')
27
+ # Make sure we don't save any of the prefs that we change.
28
+ @webView.preferences.setAutosaves(false)
29
+ # Set some useful options.
30
+ @webView.preferences.setShouldPrintBackgrounds(true)
31
+ @webView.preferences.setJavaScriptCanOpenWindowsAutomatically(false)
32
+ @webView.preferences.setAllowsAnimatedImages(false)
33
+ # Make sure we don't get a scroll bar.
34
+ @webView.mainFrame.frameView.setAllowsScrolling(false)
35
+
36
+ self
37
+ end
38
+
39
+ def fetch(url)
40
+ # This sets up the webView_* methods to be called when loading finishes.
41
+ @webView.setFrameLoadDelegate(self)
42
+ # Tell the webView what URL to load.
43
+ @webView.setValue_forKey(url, 'mainFrameURL')
44
+ # Pass control to Cocoa for a bit.
45
+ OSX.CFRunLoopRun
46
+ @succeeded
47
+ end
48
+
49
+ attr_reader :error
50
+
51
+ def webView_didFinishLoadForFrame(view, frame)
52
+ @succeeded = true
53
+
54
+ # Resize the view to fit the page.
55
+ @docView = @webView.mainFrame.frameView.documentView
56
+ @docView.window.setContentSize(@docView.bounds.size)
57
+ @docView.setFrame(@docView.bounds)
58
+
59
+ # Return control to the fetch method.
60
+ OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
61
+ end
62
+
63
+ def webView_didFailLoadWithError_forFrame(webview, error, frame)
64
+ @error = error
65
+ @succeeed = false
66
+ # Return control to the fetch method.
67
+ OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
68
+ end
69
+
70
+ def webView_didFailProvisionalLoadWithError_forFrame(webview, error, frame)
71
+ @error = error
72
+ @succeeed = false
73
+ # Return control to the fetch method.
74
+ OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
75
+ end
76
+
77
+ def save(filename, options = {})
78
+ @docView.setNeedsDisplay(true)
79
+ @docView.displayIfNeeded
80
+ @docView.lockFocus
81
+ bitmap = OSX::NSBitmapImageRep.alloc.initWithFocusedViewRect(@docView.bounds)
82
+ @docView.unlockFocus
83
+
84
+ # Write the bitmap to a file as a PNG
85
+ bitmap.representationUsingType_properties(OSX::NSPNGFileType, nil).writeToFile_atomically(filename, true)
86
+ bitmap.release
87
+ end
88
+
89
+ end
90
+
data/trials/snapper.rb ADDED
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Thanks to the heavy lifting done by others:
4
+ # http://pastie.caboo.se/69235
5
+ # http://pastie.caboo.se/68511
6
+
7
+ require 'osx/cocoa'
8
+ OSX.require_framework 'WebKit'
9
+
10
+ class Snapper < OSX::NSObject
11
+
12
+ def init
13
+ # This sets up some context that we need for creating windows.
14
+ OSX::NSApplication.sharedApplication
15
+
16
+ # Create an offscreen window into which we can stick our WebView.
17
+ #@window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
18
+ # [0, 0, 800, 600], OSX::NSBorderlessWindowMask, OSX::NSBackingStoreBuffered, false
19
+ #)
20
+ rect = [-16000.0, -16000.0, 100, 100]
21
+ @window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer(rect, OSX::NSBorderlessWindowMask, OSX::NSBackingStoreBuffered, false )
22
+
23
+
24
+
25
+ # Create a WebView and stick it in our offscreen window.
26
+ #@webView = OSX::WebView.alloc.initWithFrame([0, 0, 800, 600])
27
+ @webView = OSX::WebView.alloc.initWithFrame(rect)
28
+ @window.setContentView(@webView)
29
+
30
+ # Use the screen stylesheet, rather than the print one.
31
+ @webView.setMediaStyle('screen')
32
+ # Make sure we don't save any of the prefs that we change.
33
+ @webView.preferences.setAutosaves(false)
34
+ # Set some useful options.
35
+ @webView.preferences.setShouldPrintBackgrounds(true)
36
+ @webView.preferences.setJavaScriptCanOpenWindowsAutomatically(false)
37
+ @webView.preferences.setAllowsAnimatedImages(false)
38
+ # Make sure we don't get a scroll bar.
39
+ @webView.mainFrame.frameView.setAllowsScrolling(false)
40
+
41
+ self
42
+ end
43
+
44
+ def fetch(url)
45
+ # This sets up the webView_* methods to be called when loading finishes.
46
+ @webView.setFrameLoadDelegate(self)
47
+ # Tell the webView what URL to load.
48
+ @webView.setValue_forKey(url, 'mainFrameURL')
49
+ # Pass control to Cocoa for a bit.
50
+ OSX.CFRunLoopRun
51
+ @succeeded
52
+ end
53
+
54
+ attr_reader :error
55
+
56
+ def webView_didFinishLoadForFrame(view, frame)
57
+ @succeeded = true
58
+
59
+ # Resize the view to fit the page.
60
+ @docView = @webView.mainFrame.frameView.documentView
61
+ @docView.window.orderFront(nil)
62
+ @docView.display
63
+ @docView.window.setContentSize(@docView.bounds.size)
64
+ @docView.setFrame(@docView.bounds)
65
+ sleep(4)
66
+ # Return control to the fetch method.
67
+ OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
68
+ end
69
+
70
+ def webView_didFailLoadWithError_forFrame(webview, error, frame)
71
+ @error = error
72
+ @succeeed = false
73
+ # Return control to the fetch method.
74
+ OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
75
+ end
76
+
77
+ def webView_didFailProvisionalLoadWithError_forFrame(webview, error, frame)
78
+ @error = error
79
+ @succeeed = false
80
+ # Return control to the fetch method.
81
+ OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
82
+ end
83
+
84
+ def save(filename, options = {})
85
+ @docView.setNeedsDisplay(true)
86
+ @docView.displayIfNeeded
87
+ @docView.lockFocus
88
+ bitmap = OSX::NSBitmapImageRep.alloc.initWithFocusedViewRect(@docView.bounds)
89
+ @docView.unlockFocus
90
+
91
+ # Write the bitmap to a file as a PNG
92
+ bitmap.representationUsingType_properties(OSX::NSPNGFileType, nil).writeToFile_atomically(filename, true)
93
+ bitmap.release
94
+ end
95
+
96
+ end
97
+
@@ -0,0 +1,101 @@
1
+ # crafterm@redartisan.com, with major help from vastheman :)
2
+
3
+ require 'osx/cocoa'
4
+ require 'logger'
5
+
6
+ OSX.require_framework 'WebKit'
7
+
8
+ class Shooter
9
+
10
+ def initialize
11
+ @logger = Logger.new(STDOUT)
12
+
13
+ @pool = OSX::NSAutoreleasePool.alloc.init
14
+
15
+ # get NSApplication code ready for action
16
+ OSX.NSApplicationLoad
17
+
18
+ # Make an offscreen window
19
+ # The frame is in screen coordinates, but it's irrelevant since we'll never display it
20
+ # We want a buffered backing store, simply so we can read it later
21
+ # We don't want to defer, or it won't draw until it gets on the screen
22
+ @window = OSX::NSWindow.alloc.initWithContentRect_styleMask_backing_defer(
23
+ OSX::NSRect.new(0, 0, 1024, 768), OSX::NSBorderlessWindowMask, OSX::NSBackingStoreBuffered, false
24
+ )
25
+
26
+ # Make a web @view - the frame here is in window coordinates
27
+ @view = OSX::WebView.alloc.initWithFrame(OSX::NSRect.new(0, 0, 1024, 768))
28
+ @view.mainFrame.frameView.setAllowsScrolling(false)
29
+
30
+ # This @delegate will get a message when the load completes
31
+ @delegate = SimpleLoadDelegate.alloc.init
32
+ @delegate.shooter = self
33
+ @view.setFrameLoadDelegate(@delegate)
34
+
35
+ # Replace the window's content @view with the web @view
36
+ @window.setContentView(@view)
37
+ @view.release
38
+ end
39
+
40
+ def capture(uri, path)
41
+ # Tell the frame to load the URL we want
42
+ @view.mainFrame.loadRequest(OSX::NSURLRequest.requestWithURL(OSX::NSURL.URLWithString(uri)))
43
+
44
+ # Run the main event loop until the frame loads
45
+ OSX.CFRunLoopRun
46
+
47
+ upon_success do |view| # Capture the content of the view as an image
48
+ view.setNeedsDisplay(true)
49
+ view.displayIfNeeded
50
+ view.lockFocus
51
+ bitmap = OSX::NSBitmapImageRep.alloc.initWithFocusedViewRect(view.bounds)
52
+ view.unlockFocus
53
+
54
+ # Write the bitmap to a file as a PNG
55
+ bitmap.representationUsingType_properties(OSX::NSPNGFileType, nil).writeToFile_atomically(path, true)
56
+ bitmap.release
57
+ end
58
+
59
+ upon_failure do |error, logger|
60
+ logger.warn("Unable to load URI: #{uri} (#{error})")
61
+ end
62
+ end
63
+
64
+ def release
65
+ @window.release
66
+ @delegate.release
67
+ @pool.release
68
+ end
69
+
70
+ attr_accessor :load_success, :load_error
71
+
72
+ private
73
+
74
+ def upon_success(&block)
75
+ block.call(@view) if @load_success
76
+ end
77
+
78
+ def upon_failure(&block)
79
+ block.call(@load_error.localizedDescription, @logger) unless @load_success
80
+ end
81
+
82
+ class SimpleLoadDelegate < OSX::NSObject
83
+
84
+ attr_accessor :shooter
85
+
86
+ def webView_didFinishLoadForFrame(sender, frame)
87
+ @shooter.load_success = true; OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
88
+ end
89
+
90
+ def webView_didFailLoadWithError_forFrame(webview, load_error, frame)
91
+ @shooter.load_success = false; @shooter.load_error = load_error; OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
92
+ end
93
+
94
+ def webView_didFailProvisionalLoadWithError_forFrame(webview, load_error, frame)
95
+ @shooter.load_success = false; @shooter.load_error = load_error; OSX.CFRunLoopStop(OSX.CFRunLoopGetCurrent)
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/webshooter/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "webshooter"
6
+ s.version = Webshooter::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Patrick Debois"]
9
+ s.email = ["patrick.debois@jedi.be"]
10
+ s.homepage = "http://github.com/jedi4ever/webshooter/"
11
+ s.summary = %q{Create webshot using webkit on MacOSX}
12
+ s.description = %q{This library allows you to create webshots using webkit on MacOSX. A webshot is a screenshot taking inside the browser. The advantage of this library is that it is headless and gives you the real view not a parsed view}
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+ s.rubyforge_project = "webshooter"
16
+
17
+ s.add_development_dependency "bundler", ">= 1.0.0"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
21
+ s.require_path = 'lib'
22
+ end
23
+
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: webshooter
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: true
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1a
9
+ version: 0.0.1a
10
+ platform: ruby
11
+ authors:
12
+ - Patrick Debois
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-19 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ version_requirements: &id001 !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ segments:
26
+ - 1
27
+ - 0
28
+ - 0
29
+ version: 1.0.0
30
+ requirement: *id001
31
+ name: bundler
32
+ prerelease: false
33
+ type: :development
34
+ description: This library allows you to create webshots using webkit on MacOSX. A webshot is a screenshot taking inside the browser. The advantage of this library is that it is headless and gives you the real view not a parsed view
35
+ email:
36
+ - patrick.debois@jedi.be
37
+ executables:
38
+ - webshooter.rb
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - Gemfile.lock
47
+ - README.txt
48
+ - Rakefile
49
+ - bin/webshooter.rb
50
+ - lib/webshooter.rb
51
+ - lib/webshooter/newnsurlrequest.rb
52
+ - lib/webshooter/version.rb
53
+ - lib/webshooter/webshotprocessor.rb
54
+ - trials/darkroom.orig.rb
55
+ - trials/darkroom.rb
56
+ - trials/snapper.orig.rb
57
+ - trials/snapper.rb
58
+ - trials/webkit-shooter.orig.rb
59
+ - webshooter.gemspec
60
+ has_rdoc: true
61
+ homepage: http://github.com/jedi4ever/webshooter/
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options: []
66
+
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 1
82
+ - 3
83
+ - 6
84
+ version: 1.3.6
85
+ requirements: []
86
+
87
+ rubyforge_project: webshooter
88
+ rubygems_version: 1.3.6
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Create webshot using webkit on MacOSX
92
+ test_files: []
93
+