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 +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +14 -0
- data/README.txt +32 -0
- data/Rakefile +5 -0
- data/bin/webshooter.rb +18 -0
- data/lib/webshooter/newnsurlrequest.rb +9 -0
- data/lib/webshooter/version.rb +3 -0
- data/lib/webshooter/webshotprocessor.rb +224 -0
- data/lib/webshooter.rb +13 -0
- data/trials/darkroom.orig.rb +112 -0
- data/trials/darkroom.rb +81 -0
- data/trials/snapper.orig.rb +90 -0
- data/trials/snapper.rb +97 -0
- data/trials/webkit-shooter.orig.rb +101 -0
- data/webshooter.gemspec +23 -0
- metadata +93 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.png
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
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
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,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
|
data/trials/darkroom.rb
ADDED
@@ -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
|
+
|
data/webshooter.gemspec
ADDED
@@ -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
|
+
|