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# ruby ./darkroom.rb --width=400 --delay=5 http://yahoo.com
14#
15require 'optparse'
16require 'osx/cocoa'
17OSX.require_framework 'Webkit'
18
19module ActiveReload
20  module DarkRoom
21    USER_AGENT = "DarkRoom/0.1"
22    class Photographer
23      def initialize
24        options = {}
25        opts = OptionParser.new do |opts|
26          opts.banner = "Usage: #$0 [options] URL"
27      
28          opts.on('-w', '--width=[WIDTH]', Integer, 'Force width of the screenshot') do |v|
29            options[:width] = v
30          end
31      
32          opts.on('-h', '--height=[HEIGHT]', Integer, 'Force height of screenshot') do |v|
33            options[:height] = v
34          end
35          
36          opts.on('-o', '--output=[FILENAME]', String, 'Specify filename for saving') do |v|
37            options[:output] = v
38          end
39          
40          opts.on('-d', '--delay=[DELAY]', Integer, 'Delay in seconds to give web page assets time to load') do |v|
41            options[:delay] = v
42          end
43      
44          opts.on_tail('-h', '--help', 'Display this message and exit') do
45            puts opts
46            exit
47          end
48        end.parse!
49        options[:width]  ||= 1024
50        options[:height] ||= 0
51        options[:website] = ARGV.first || 'http://google.com'
52        Camera.shoot(options)
53      end
54    end
55
56    class Camera
57      def self.shoot(options)
58        app = OSX::NSApplication.sharedApplication
59        delegate = Processor.alloc.init
60        delegate.options = options
61        app.setDelegate(delegate)        
62        app.run
63      end
64    end
65
66    class Processor < OSX::NSObject
67      include OSX
68      attr_accessor :options, :web_view
69  
70      def initialize
71        rect = [-16000.0, -16000.0, 100, 100]
72        win = NSWindow.alloc.initWithContentRect_styleMask_backing_defer(rect, NSBorderlessWindowMask, 2, 0)
73    
74        @web_view = WebView.alloc.initWithFrame(rect)
75        @web_view.mainFrame.frameView.setAllowsScrolling(false)
76        @web_view.setApplicationNameForUserAgent(USER_AGENT)
77        @web_view.setFrameLoadDelegate(self)
78    
79        win.setContentView(@web_view) 
80      end
81      
82      def applicationDidFinishLaunching(notification)
83        @options[:output] ||= "#{Time.now.strftime('%m-%d-%y-%H%I%S')}.png"
84        @web_view.window.setContentSize([@options[:width], @options[:height]])
85        @web_view.setFrameSize([@options[:width], @options[:height]])
86        @web_view.mainFrame.loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(@options[:website])))
87      end
88  
89      def webView_didFinishLoadForFrame(web_view, frame)
90        viewport = web_view.mainFrame.frameView.documentView
91        viewport.window.orderFront(nil)
92        viewport.window.display
93        viewport.window.setContentSize([@options[:width], (@options[:height] > 0 ? @options[:height] : viewport.bounds.height)])
94        viewport.setFrame(viewport.bounds)
95        sleep(@options[:delay]) if @options[:delay]
96        capture_and_save(viewport)
97      end
98  
99      def capture_and_save(view)
100        view.lockFocus
101          bitmap = NSBitmapImageRep.alloc.initWithFocusedViewRect(view.bounds)
102        view.unlockFocus
103    
104        bitmap.representationUsingType_properties(NSPNGFileType, nil).writeToFile_atomically(@options[:output], true)
105        NSApplication.sharedApplication.terminate(nil)
106      end
107    end
108  end
109end
110ActiveReload::DarkRoom::Photographer.new
111