<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/rss2full.xsl" type="text/xsl" media="screen"?><?xml-stylesheet href="http://feeds.feedburner.com/~d/styles/itemcontent.css" type="text/css" media="screen"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">

<channel>
	<title>tlrobinson.net / blog</title>
	
	<link>http://tlrobinson.net/blog</link>
	<description />
	<pubDate>Fri, 14 Nov 2008 06:20:30 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.7</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" href="http://feeds.feedburner.com/tlrobinson" type="application/rss+xml" /><item>
		<title>Ant Tasks for Git</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/452607453/</link>
		<comments>http://tlrobinson.net/blog/2008/11/13/ant-tasks-for-git/#comments</comments>
		<pubDate>Fri, 14 Nov 2008 06:20:30 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Git]]></category>

		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=75</guid>
		<description><![CDATA[Ant has tasks for CVS and Subversion, but none that I could find for Git. I threw together these simple Ant macros to get started:

&#60;macrodef name = &#34;git&#34;&#62;
&#160;&#160;&#160;&#160;&#60;attribute name = &#34;command&#34; /&#62;
&#160;&#160;&#160;&#160;&#60;attribute name = &#34;dir&#34; default = &#34;&#34; /&#62;
&#160;&#160;&#160;&#160;&#60;element name = &#34;args&#34; optional = &#34;true&#34; /&#62;
&#160;&#160;&#160;&#160;&#60;sequential&#62;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#60;echo message = &#34;git @{command}&#34; /&#62;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#60;exec executable = &#34;git&#34; dir [...]]]></description>
			<content:encoded><![CDATA[<p>Ant has tasks for CVS and Subversion, but none that I could find for Git. I threw together these simple Ant macros to get started:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881280;">&lt;macrodef </span><span style="color:#994500;">name</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;git&quot;</span><span style="color:#881280;">&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;attribute </span><span style="color:#994500;">name</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;command&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;attribute </span><span style="color:#994500;">name</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;dir&quot;</span><span style="color:#881280;"> </span><span style="color:#994500;">default</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;element </span><span style="color:#994500;">name</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;args&quot;</span><span style="color:#881280;"> </span><span style="color:#994500;">optional</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;true&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;sequential&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;echo </span><span style="color:#994500;">message</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;git @{command}&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;exec </span><span style="color:#994500;">executable</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;git&quot;</span><span style="color:#881280;"> </span><span style="color:#994500;">dir</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;@{dir}&quot;</span><span style="color:#881280;">&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;arg </span><span style="color:#994500;">value</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;@{command}&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;args/&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;/exec&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;/sequential&gt;</span><br />
<span style="color:#881280;">&lt;/macrodef&gt;</span><br />
<br />
<span style="color:#881280;">&lt;macrodef </span><span style="color:#994500;">name</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;git-clone-pull&quot;</span><span style="color:#881280;">&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;attribute </span><span style="color:#994500;">name</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;repository&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;attribute </span><span style="color:#994500;">name</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;dest&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;sequential&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;git </span><span style="color:#994500;">command</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;clone&quot;</span><span style="color:#881280;">&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;args&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;arg </span><span style="color:#994500;">value</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;@{repository}&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;arg </span><span style="color:#994500;">value</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;@{dest}&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;/args&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;/git&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;git </span><span style="color:#994500;">command</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;pull&quot;</span><span style="color:#881280;"> </span><span style="color:#994500;">dir</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;@{dest}&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;/sequential&gt;</span><br />
<span style="color:#881280;">&lt;/macrodef&gt;</span></div>

<p>The first one, &#8220;git&#8221; just runs git with whatever command you provide to it (clone, pull, etc) along with any arguments you pass to it. Clone:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881280;">&lt;git </span><span style="color:#994500;">command</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;clone&quot;</span><span style="color:#881280;">&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;args&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;arg </span><span style="color:#994500;">value</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;git://github.com/280north/ojunit.git&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;arg </span><span style="color:#994500;">value</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;ojunit&quot;</span><span style="color:#881280;"> /&gt;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881280;">&lt;/args&gt;</span><br />
<span style="color:#881280;">&lt;/git&gt;</span><br />
</div>

<p>And pull:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881280;">&lt;git </span><span style="color:#994500;">command</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;pull&quot;</span><span style="color:#881280;"> </span><span style="color:#994500;">dir</span><span style="color:#881280;"> = </span><span style="color:#1a1aa6;">&quot;repository_path&quot;</span><span style="color:#881280;"> /&gt;</span></div>

<p>(Other git command will likely work, these are just the ones I needed)</p>

<p>The second one, &#8220;git-clone-pull&#8221; uses the first one to clone a repository then pull from it. This effectively clones the repository if it hasn&#8217;t already been cloned, otherwise it pulls. However, since ant is fairly limited in what sorts of conditional execution you can perform, it just does both (clone will fail if it&#8217;s already been cloned, and pull will always be executed, even immediately after a the initial clone). Obviously not ideal, but it works, and I couldn&#8217;t figure out a better way without writing actual code.</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881280;">&lt;git-clone-pull </span><span style="color:#994500;">repository</span><span style="color:#881280;">=</span><span style="color:#1a1aa6;">&quot;git://github.com/280north/ojunit.git&quot;</span><span style="color:#881280;"> </span><span style="color:#994500;">dest</span><span style="color:#881280;">=</span><span style="color:#1a1aa6;">&quot;ojunit&quot;</span><span style="color:#881280;"> /&gt;</span></div>

<p>There is plenty of room for improvement, but I suspect a proper Ant task written in Java is the right way to go.</p>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/452607453" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/11/13/ant-tasks-for-git/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/11/13/ant-tasks-for-git/</feedburner:origLink></item>
		<item>
		<title>“Mark Old As Read” for NetNewsWire</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/452413031/</link>
		<comments>http://tlrobinson.net/blog/2008/11/13/mark-old-as-read-for-netnewswire/#comments</comments>
		<pubDate>Fri, 14 Nov 2008 01:58:28 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[AppleScript]]></category>

		<category><![CDATA[Hacks]]></category>

		<category><![CDATA[Mac]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=73</guid>
		<description><![CDATA[I recently heard about an RSS reader (can&#8217;t remember which) that had a feature to mark all messages older than a certain threshold as &#8220;read&#8221;. I thought this was an incredibly useful feature, since I often forget to check my feeds for days at a time, and end up with hundreds of unread items that [...]]]></description>
			<content:encoded><![CDATA[<p>I recently heard about an RSS reader (can&#8217;t remember which) that had a feature to mark all messages older than a certain threshold as &#8220;read&#8221;. I thought this was an incredibly useful feature, since I often forget to check my feeds for days at a time, and end up with hundreds of unread items that I don&#8217;t have time to read.</p>

<p>Luckily my current RSS reader, <a href="http://www.newsgator.com/INDIVIDUALS/NETNEWSWIRE/">NetNewsWire</a>, has AppleScript built in, so I whipped up this script that prompts for the number of days you want to keep as unread, and marks the rest as read.</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#0b0bff;">tell</span> <span style="color:#0b0bff;">application</span> &quot;NetNewsWire&quot;<br />
&nbsp;&nbsp;&nbsp;&nbsp;display dialog &quot;How many days old to mark read?&quot; default answer &quot;7&quot;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#0b0bff;">set</span> numDays <span style="color:#0b0bff;">to</span> text returned <span style="color:#0b0bff;">of</span> <span style="color:#0b0bff;">result</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#0b0bff;">set</span> threshold <span style="color:#0b0bff;">to</span> (current date) - (numDays * <span style="color:#0b0bff;">days</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#0b0bff;">set</span> isRead <span style="color:#0b0bff;">of</span> (headlines <span style="color:#0b0bff;">of</span> subscriptions <span style="color:#0b0bff;">where</span> (isRead <span style="color:#0b0bff;">is</span> <span style="color:#0b0bff;">equal</span> <span style="color:#0b0bff;">to</span> <span style="color:#0b0bff;">false</span> <span style="color:#0b0bff;">and</span> date published &lt; threshold)) <span style="color:#0b0bff;">to</span> <span style="color:#0b0bff;">true</span><br />
<span style="color:#0b0bff;">end</span> <span style="color:#0b0bff;">tell</span></div>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/452413031" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/11/13/mark-old-as-read-for-netnewswire/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/11/13/mark-old-as-read-for-netnewswire/</feedburner:origLink></item>
		<item>
		<title>A Better BugMeNot Bookmarklet</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/447299705/</link>
		<comments>http://tlrobinson.net/blog/2008/11/09/a-better-bugmenot-bookmarklet/#comments</comments>
		<pubDate>Sun, 09 Nov 2008 10:46:21 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Hacks]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Ruby]]></category>

		<category><![CDATA[Ruby on Rails]]></category>

		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=71</guid>
		<description><![CDATA[BugMeNot is a great little service for bypassing the registration process for websites that really shouldn&#8217;t require it (ahem, nytimes.com). The bookmarklet brings up BugMeNot for the current website you&#8217;re viewing, and gives you login/password pairs which you can then copy and paste.

But wouldn&#8217;t it be better if it automagically filled in the username and [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.bugmenot.com/">BugMeNot</a> is a great little service for bypassing the registration process for websites that really shouldn&#8217;t require it (ahem, nytimes.com). The bookmarklet brings up BugMeNot for the current website you&#8217;re viewing, and gives you login/password pairs which you can then copy and paste.</p>

<p>But wouldn&#8217;t it be better if it automagically filled in the username and password for you? I thought so, so I wrote a few lines of code in the form of a bookmarklet and a JSONP web service to do this.</p>

<p>BugMeNot doesn&#8217;t provide an API so I had to do a little screen scraping with <a href="https://code.whytheluckystiff.net/hpricot/">Hpricot</a>. They also try to obfuscate the usernames and passwords by shifting the characters by some offset calculated from a &#8220;key&#8221; then Base64 encoding the string, and prepending 4 characters. Luckily their obfuscation was no match for a single line of Ruby:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881350;">def</span> bmn_decode(input, offset)<br />
&nbsp;&nbsp;<span style="color:#236e25;"># decode base64, strip first 4 chars, convert chars to ints, substract offset, convert back ints back to chars<br />
</span>&nbsp;&nbsp;input.unpack(<span style="color:#760f15;">&quot;m*&quot;</span>)[<span style="color:#0000ff;">0</span>][<span style="color:#0000ff;">4.</span>.<span style="color:#0000ff;">-1</span>].unpack(<span style="color:#760f15;">&quot;C*&quot;</span>).map{|c| c - offset }.pack(<span style="color:#760f15;">&quot;C*&quot;</span>)<br />
<span style="color:#881350;">end</span></div>

<p>The bookmarklet makes the request via an injected &lt;script&gt; tag. When it&#8217;s callback gets called it finds the most likely input elements for the username and password and fills them in with the result.</p>

<p>The Rails app consists of a single action that makes a request to bugmenot.com for the specified site, extracts and decodes the usernames and passwords, and picks the one with the highest rating. It then returns the result as JSON wrapped in a function callback (i.e. JSONP)</p>

<p>I&#8217;m not going to post the location of the live JSONP web service since BugMeNot limits the number of requests you can make, but the code is available <a href="http://github.com/tlrobinson/tlrobinson/tree/master/bbmn">on GitHub</a>.</p>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/447299705" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/11/09/a-better-bugmenot-bookmarklet/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/11/09/a-better-bugmenot-bookmarklet/</feedburner:origLink></item>
		<item>
		<title>Open new Terminal tab in current directory (updated!)</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/429436053/</link>
		<comments>http://tlrobinson.net/blog/2008/10/23/open-new-terminal-tab/#comments</comments>
		<pubDate>Thu, 23 Oct 2008 08:47:16 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Command line]]></category>

		<category><![CDATA[Hacks]]></category>

		<category><![CDATA[Mac]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=68</guid>
		<description><![CDATA[This is an updated shell script / AppleScript for opening a new tab in your current directory (or the specified directory). The last version was for the pre-tabbed version of Terminal.

#!/bin/sh -

if [ $# -ne 1 ]; then
&#160;&#160;&#160;&#160;PATHDIR=`pwd`
else
&#160;&#160;&#160;&#160;PATHDIR=$1
fi

/usr/bin/osascript &#60;&#60;-EOF
activate application &#34;Terminal&#34;
tell application &#34;System Events&#34;
&#160;&#160;&#160;&#160;keystroke &#34;t&#34; using {command down}
end tell
tell application &#34;Terminal&#34;
&#160;&#160;&#160;&#160;repeat with win in windows
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;try
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;if [...]]]></description>
			<content:encoded><![CDATA[<p>This is an updated shell script / AppleScript for opening a new <em>tab</em> in your current directory (or the specified directory). The <a href="http://tlrobinson.net/blog/2007/09/07/open-new-terminal-window-in-current-or-other-specified-directory/">last version</a> was for the pre-tabbed version of Terminal.</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;">#!/bin/sh -<br />
</span><br />
<span style="color:#881350;">if</span> [ <span style="color:#c4620a;">$#</span> -ne <span style="color:#0000ff;">1</span> ]; <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;PATHDIR=<span style="color:#660088;">`pwd`</span><br />
<span style="color:#881350;">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;PATHDIR=<span style="color:#c4620a;">$1</span><br />
<span style="color:#881350;">fi</span><br />
<br />
/usr/bin/osascript &lt;&lt;-EOF<br />
activate application <span style="color:#760f15;">&quot;Terminal&quot;</span><br />
tell application <span style="color:#760f15;">&quot;System Events&quot;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;keystroke <span style="color:#760f15;">&quot;t&quot;</span> using {<span style="color:#440088;">command</span> down}<br />
end tell<br />
tell application <span style="color:#760f15;">&quot;Terminal&quot;</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;repeat with win <span style="color:#881350;">in</span> windows<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;try<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span> get frontmost of win is <span style="color:#880088;">true</span> <span style="color:#881350;">then</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">do</span> script <span style="color:#760f15;">&quot;cd </span><span style="color:#c4620a;">$PATHDIR</span><span style="color:#760f15;">; clear&quot;</span> <span style="color:#881350;">in</span><span style="color:#003369;"> </span>(selected tab of win)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end <span style="color:#881350;">if</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end try<br />
&nbsp;&nbsp;&nbsp;&nbsp;end repeat<br />
end tell<br />
EOF<br />
</div>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/429436053" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/10/23/open-new-terminal-tab/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/10/23/open-new-terminal-tab/</feedburner:origLink></item>
		<item>
		<title>Automagically Wrapping JavaScript Callback Functions</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/428471631/</link>
		<comments>http://tlrobinson.net/blog/2008/10/22/wrapping-javascript-callbacks/#comments</comments>
		<pubDate>Wed, 22 Oct 2008 11:43:30 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Hacks]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Uncategorized]]></category>

		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=59</guid>
		<description><![CDATA[One very nice thing about JavaScript is it&#8217;s support for first-class functions and closures. Crockford calls JavaScript &#8220;Lisp in C&#8217;s Clothing&#8221;. I&#8217;m no Lisper, but I enjoy I discovering new tricks or applications of functional programming in JavaScript.

I wanted to hook all the browser&#8217;s asynchronous JavaScript &#8220;entry points&#8221; : events, timers, asynchronous XMLHttpRequests, script tags, [...]]]></description>
			<content:encoded><![CDATA[<p>One very nice thing about JavaScript is it&#8217;s support for first-class functions and closures. Crockford calls JavaScript <a href="http://www.crockford.com/javascript/javascript.html">&#8220;Lisp in C&#8217;s Clothing&#8221;</a>. I&#8217;m no Lisper, but I enjoy I discovering new tricks or applications of functional programming in JavaScript.</p>

<p>I wanted to hook all the browser&#8217;s asynchronous JavaScript &#8220;entry points&#8221; : events, timers, asynchronous XMLHttpRequests, script tags, and &#8220;javascript:&#8221; URLs. This article deals with the first two, events and timers.</p>

<p>To do this, I figured I could somehow &#8220;wrap&#8221; all the callback functions passed to setTimeout(), setInterval(), and addEventListener(). By &#8220;wrapped&#8221; I mean another function that <em>we specify</em> calls the original function, rather than the original function getting called directly. This allows us do whatever we want right before and after calling the original function, including manipulating the arguments and return value, logging to the console, calling other functions, putting it in a try/catch, etc.</p>

<p>Here&#8217;s the implementation of &#8220;callbackWrap&#8221;, the function that does all the work:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881350;">function</span> <span style="color:#003369;">callbackWrap</span>(object, property, argumentIndex, wrapperFactory, extra) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> original = object[property];<br />
&nbsp;&nbsp;&nbsp;&nbsp;object[property] = <span style="color:#881350;">function</span>() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;arguments[argumentIndex] = <span style="color:#003369;">wrapperFactory</span>(arguments[argumentIndex], extra);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> original.<span style="color:#003369;">apply</span>(<span style="color:#881350;">this</span>, arguments);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> original;<br />
}</div>

<p>To use it, you need function that takes another function as a parameter and returns a wrapped version of that function which does whatever you want it to. In this case it just prints out &#8220;whoooaaaaa:&#8221; followed by the &#8220;extra&#8221; parameter:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881350;">function</span> <span style="color:#003369;">logWrapper</span>(func, extra) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> <span style="color:#881350;">function</span>() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;console.<span style="color:#003369;">log</span>(<span style="color:#760f15;">&quot;whoooaaaaa: &quot;</span> + extra);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> func.<span style="color:#003369;">apply</span>(<span style="color:#881350;">this</span>, arguments);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}</div>

<p>Finally, to actually use this, we call callbackWrap with the object that contains the function we want to wrap, the name of the function property, the index of the callback argument to the function (0 for setTimeout/setInterval, 1 for addEventListener), a wrapper &#8220;factory&#8221; function, and optionally an extra argument that&#8217;s made available (via the closure) to the wrapped function. The extra parameter can be used for any data you need to pass to the wrapper function, or it can be ignored.</p>

<p>Here&#8217;s how this would be used on setTimeout, setInterval, and addEventListener on window, Element.prototype and Document.prototype (so it applies to all Elements and Documents): </p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#003369;">callbackWrap</span>(window, <span style="color:#760f15;">&quot;setTimeout&quot;</span>, <span style="color:#0000ff;">0</span>, logWrapper, <span style="color:#760f15;">&quot;wrapped a window.setTimeout!&quot;</span>);<br />
<span style="color:#003369;">callbackWrap</span>(window, <span style="color:#760f15;">&quot;setInterval&quot;</span>, <span style="color:#0000ff;">0</span>, logWrapper, <span style="color:#760f15;">&quot;wrapped a window.setInterval!&quot;</span>);<br />
<span style="color:#003369;">callbackWrap</span>(window, <span style="color:#760f15;">&quot;addEventListener&quot;</span>, <span style="color:#0000ff;">1</span>, logWrapper, <span style="color:#760f15;">&quot;wrapped a window.addEventListener!&quot;</span>);<br />
<span style="color:#003369;">callbackWrap</span>(Element.prototype, <span style="color:#760f15;">&quot;addEventListener&quot;</span>, <span style="color:#0000ff;">1</span>, logWrapper, <span style="color:#760f15;">&quot;wrapped a Element.addEventListener!&quot;</span>);<br />
<span style="color:#003369;">callbackWrap</span>(Document.prototype, <span style="color:#760f15;">&quot;addEventListener&quot;</span>, <span style="color:#0000ff;">1</span>, logWrapper, <span style="color:#760f15;">&quot;wrapped a Document.addEventListener!&quot;</span>);</div>

<p><a href="http://tlrobinson.net/misc/wrap.html">Here&#8217;s a live example.</a> <em>(edit: the button example seems to be broken in Firefox)</em></p>

<p>The first thing callbackWrap does is save the original function (window.setTimeout, for this example) in a local variable, &#8220;original&#8221;, since we&#8217;ll need it later. We then replace the original with a new function. When this new function is called (the new window.setTimeout), we first call the &#8220;wrapperMaker&#8221; function (&#8221;logWrapper&#8221; in the example), passing it the callback function, which is the argument at the argumentIndex position, and the optional &#8220;extra&#8221; argument. That function returns a new function which replaces the original callback argument, before we then call the original function (saved in the local variable &#8220;original&#8221;) with the same arguments (except the callback is now wrapped).</p>

<p>So really this blog post should have been called <em>&#8220;a function that wraps other functions such that all callback functions passed to it are wrapped by yet another function&#8221;</em>. There&#8217;s <em>two</em> levels of wrapping going on: the original function is wrapped, such that <em>it</em> wraps every <em>callback</em> given to it. If you&#8217;re confused I don&#8217;t blame you.</p>

<p>Why would I want to do this, you ask? There are a couple scenarios I had in mind, and I&#8217;m sure you can think of others.</p>

<p>First, I wanted to catch all uncaught exceptions for a debugging tool I&#8217;m working on. Sure, you can assign an error handler to the &#8220;onerror&#8221; property of the window object, but this only gives you the error name, file name, and line number – I wanted the full exception object to be caught by a try/catch.</p>

<p>Here&#8217;s a wrapper that catches all uncaught exceptions and alerts them:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881350;">function</span> <span style="color:#003369;">exceptionWrapper</span>(func, extra) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> <span style="color:#881350;">function</span>() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">try</span> {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> func.<span style="color:#003369;">apply</span>(<span style="color:#881350;">this</span>, arguments);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <span style="color:#881350;">catch</span><span style="color:#003369;"> </span>(e) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">alert</span>(e);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}</div>

<p>Second, in <a href="http://cappuccino.org">Cappuccino</a> we mimic Cocoa&#8217;s concept of a &#8220;run loop&#8221;, but due to the way browsers work the implementation is a bit different. A consequence of this was that we couldn&#8217;t automatically call &#8220;[[CPRunLoop currentRunLoop] performSelectors];&#8221; at the end of each run loop, which is required for various pieces of Cappuccino. If you&#8217;ve ever come across a situation where you perform some action but the UI doesn&#8217;t update until you move the mouse, it&#8217;s probably because performSelectors isn&#8217;t being called. This isn&#8217;t common, since Cappuccino encapsulates events, XMLHttpRequests, and now timers with CPEvent, CPURLConnection, and CPTimer, respectively. These Cappuccino classes handle calling performSelectors for you. However, if you wanted to integrate with a third party library or use setTimeout, XMLHttpRequest, etc directly, currently you would need to call performSelectors manually. With this new trick we can call it automagically.</p>

<p>Here&#8217;s the wrapper that does exactly that (note the inline Objective-J call to &#8220;[[CPRunLoop currentRunLoop] performSelectors]&#8221;):</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881350;">function</span> <span style="color:#003369;">performSelectorsWrapper</span>(func, extra) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> <span style="color:#881350;">function</span>() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> result = func.<span style="color:#003369;">apply</span>(<span style="color:#881350;">this</span>, arguments);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> [[CPRunLoop currentRunLoop] performSelectors];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}</div>

<p>One minor feature I didn&#8217;t demonstrate was that callbackWrap returns the original function that it wrapped. If you need the original for any reason you can save it to some other variable.</p>

<p>callbackWrap could use a few improvements. Currently this breaks on cases where you pass a string instead of a function. A simple check for the argument type, and appropriate handling would solve this (either skipping the wrapping, or compiling the the string with a &#8220;new Function()&#8221; call). But using strings instead of functions is considered poor practice anyway.</p>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/428471631" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/10/22/wrapping-javascript-callbacks/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/10/22/wrapping-javascript-callbacks/</feedburner:origLink></item>
		<item>
		<title>Improved Browser Paint Events Bookmarklet</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/428471632/</link>
		<comments>http://tlrobinson.net/blog/2008/10/14/improved-browser-paint-events-bookmarklet/#comments</comments>
		<pubDate>Tue, 14 Oct 2008 08:27:08 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Bookmarklet]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=56</guid>
		<description><![CDATA[John Resig posted today about a nifty new feature available in Firefox nightlies, browser paint events. He also posted an example script and bookmarklet called TrackPaint. He goes into greater depth in his post, so I won&#8217;t bother here.

I wanted something more &#8220;real-time&#8221; and closer to the Quartz Debug utility included with the Mac OS [...]]]></description>
			<content:encoded><![CDATA[<p>John Resig posted today about a nifty new feature available in Firefox nightlies, <a href="http://ejohn.org/blog/browser-paint-events/">browser paint events</a>. He also posted an example script and bookmarklet called TrackPaint. He goes into greater depth in his post, so I won&#8217;t bother here.</p>

<p>I wanted something more &#8220;real-time&#8221; and closer to the Quartz Debug utility included with the Mac OS X developer tools (which essentially provides this feature for all of OS X), so I present my modified <a href="javascript:(function(s){s.src='http://tlrobinson.net/misc/track.js';document.body.appendChild(s);})(document.createElement('script'));">bookmarklet</a> and <a href="http://tlrobinson.net/misc/track.js">code</a>:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;">// modified from John Resig&#8217;s example: http://ejohn.org/apps/paint/track.js<br />
</span>(<span style="color:#881350;">function</span>(){<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> container;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;window.<span style="color:#003369;">addEventListener</span>(<span style="color:#760f15;">&quot;MozAfterPaint&quot;</span>, log, <span style="color:#881350;">false</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>( document.body )<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">bind</span>();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window.<span style="color:#003369;">addEventListener</span>(<span style="color:#760f15;">&quot;load&quot;</span>, bind, <span style="color:#881350;">false</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">function</span> <span style="color:#003369;">bind</span>()<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container = document.<span style="color:#003369;">createElement</span>(<span style="color:#760f15;">&quot;div&quot;</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;document.body.<span style="color:#003369;">appendChild</span>(container);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">function</span> <span style="color:#003369;">log</span>(e)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window.<span style="color:#003369;">removeEventListener</span>(<span style="color:#760f15;">&quot;MozAfterPaint&quot;</span>, log, <span style="color:#881350;">false</span>);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> rects = e.clientRects;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>( <span style="color:#881350;">var</span> i = <span style="color:#0000ff;">0</span>; i &lt; rects.length; i++ ) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> rect = rects[i];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> div = document.<span style="color:#003369;">createElement</span>(<span style="color:#760f15;">&quot;div&quot;</span>);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">with</span><span style="color:#003369;"> </span>(div.style) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;background = <span style="color:#760f15;">&quot;red&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;opacity = <span style="color:#760f15;">&quot;0.1&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;position = <span style="color:#760f15;">&quot;absolute&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;top = rect.top + <span style="color:#760f15;">&quot;px&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;left = rect.left + <span style="color:#760f15;">&quot;px&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;width = (rect.right - rect.left) + <span style="color:#760f15;">&quot;px&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;height = (rect.bottom - rect.top) + <span style="color:#760f15;">&quot;px&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container.<span style="color:#003369;">appendChild</span>(div);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window.<span style="color:#003369;">setTimeout</span>(<span style="color:#881350;">function</span>() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">while</span><span style="color:#003369;"> </span>(container.firstChild)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container.<span style="color:#003369;">removeChild</span>(container.firstChild);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window.<span style="color:#003369;">setTimeout</span>(<span style="color:#881350;">function</span>() {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;window.<span style="color:#003369;">addEventListener</span>(<span style="color:#760f15;">&quot;MozAfterPaint&quot;</span>, log, <span style="color:#881350;">false</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}, <span style="color:#0000ff;">0</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}, <span style="color:#0000ff;">100</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
})();<br />
</div>

<p>Rather than recording each event and displaying them when you click, this version immediately disables the MozAfterPaint event listener (to avoid the recursion issue), shows the translucent red divs, waits 100 ms, removes the rectangles, and re-enables the MozAfterPaint event listener.</p>

<p>It will miss some events during the 100 ms flash, but overall it seems to work pretty well. You could modify it to re-enable the event listener between adding and removing the divs, but I&#8217;m not sure it&#8217;s worth the effort.</p>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/428471632" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/10/14/improved-browser-paint-events-bookmarklet/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/10/14/improved-browser-paint-events-bookmarklet/</feedburner:origLink></item>
		<item>
		<title>Command line interpreter and REPL for JSCocoa</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/428471633/</link>
		<comments>http://tlrobinson.net/blog/2008/10/10/command-line-interpreter-and-repl-for-jscocoa/#comments</comments>
		<pubDate>Fri, 10 Oct 2008 10:08:07 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[C]]></category>

		<category><![CDATA[Command line]]></category>

		<category><![CDATA[Hacks]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[Mac]]></category>

		<category><![CDATA[Objective-C]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=55</guid>
		<description><![CDATA[A few months ago I started working on a JavaScript to Objective-C bridge. We had already implemented Objective-C in JavaScript, so I figured &#8220;why not?&#8221;

Well, I never got very far, but thankfully Patrick Geiller apparently had the same idea and actually executed it: He announced JSCocoa today. It looks like it&#8217;s a solid bridge, about [...]]]></description>
			<content:encoded><![CDATA[<p>A few months ago I started working on a JavaScript to Objective-C bridge. We had already <a href="http://cappuccino.org">implemented Objective-C in JavaScript</a>, so I figured &#8220;why not?&#8221;</p>

<p>Well, I never got very far, but thankfully <a href="http://parmanoir.com/">Patrick Geiller</a> apparently had the same idea and actually executed it: He <a href="http://parmanoir.com/JSCocoa%2C_a_bridge_from_Javascript_to_Cocoa">announced</a> <a href="http://inexdo.com/JSCocoa">JSCocoa</a> today. It looks like it&#8217;s a solid bridge, about up to par with <a href="http://pyobjc.sourceforge.net/">PyObjC</a> and <a href="http://rubycocoa.sourceforge.net/">RubyCocoa</a>.</p>

<p>While the included GUI interface for trying out JSCocoa is nice, I prefer command line interfaces for my languages, so I ripped out the few lines of code from my original bridge and plugged in JSCocoa.</p>

<p><a href="http://github.com/tlrobinson/tlrobinson/tree/master/jscocoa">Code and build instructions on GitHub</a>.</p>

<p>It&#8217;s very bare bones at the moment: it will either read one or more file names from the command line arguments, or if no arguments are supplied it will present a no-frills REPL. Obviously line-editing, etc would be one of the next steps, but for now it works nicely with <a href="http://http://tlrobinson.net/blog/?p=32">rlwrap</a>.</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#683821;">#import &lt;Foundation/Foundation.h&gt;<br />
#import &quot;JSCocoaController.h&quot;<br />
</span><br />
<span style="color:#881350;">void</span> <span style="color:#003369;">JSValuePrint</span>(JSContextRef, JSValueRef, JSValueRef *);<br />
<br />
<span style="color:#881350;">int</span> <span style="color:#003369;">main </span>(<span style="color:#881350;">int</span> argc, <span style="color:#881350;">const</span> <span style="color:#881350;">char</span> * argv[])<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;[[<span style="color:#400080;">NSAutoreleasePool</span> <span style="color:#ff0000;">alloc</span>] <span style="color:#6c0540;">init</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">id</span> c = [JSCocoaController <span style="color:#6c0540;">sharedController</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;JSGlobalContextRef ctx = [c <span style="color:#6c0540;">ctx</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(argc &gt; <span style="color:#0000ff;">1</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">int</span> i = <span style="color:#0000ff;">1</span>; i &lt; argc; i++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[c <span style="color:#6c0540;">evalJSFile:</span>[<span style="color:#400080;">NSString</span> <span style="color:#6c0540;">stringWithFormat:</span><span style="color:#760f15;">@&quot;%s&quot;</span>, argv[i]]];<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">while</span><span style="color:#003369;"> </span>(<span style="color:#0000ff;">1</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">char</span> buffer[<span style="color:#0000ff;">1024</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">printf</span>(<span style="color:#760f15;">&quot;js&gt; &quot;</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(<span style="color:#003369;">fgets</span>(buffer, <span style="color:#0000ff;">1024</span>, stdin) == <span style="color:#881350;">NULL</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">exit</span>(<span style="color:#0000ff;">0</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JSStringRef script = <span style="color:#003369;">JSStringCreateWithUTF8CString</span>(buffer);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JSValueRef exception = <span style="color:#881350;">NULL</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(<span style="color:#003369;">JSCheckScriptSyntax</span>(ctx, script, <span style="color:#0000ff;">0</span>, <span style="color:#0000ff;">0</span>, &amp;exception) &amp;&amp; !exception)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JSValueRef value = <span style="color:#003369;">JSEvaluateScript</span>(ctx, script, <span style="color:#0000ff;">0</span>, <span style="color:#0000ff;">0</span>, <span style="color:#0000ff;">0</span>, &amp;exception);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(exception)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">JSValuePrint</span>(ctx, exception, <span style="color:#881350;">NULL</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(value &amp;&amp; !<span style="color:#003369;">JSValueIsUndefined</span>(ctx, value))<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">JSValuePrint</span>(ctx, value, &amp;exception);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">printf</span>(<span style="color:#760f15;">&quot;Syntax error\n&quot;</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">JSStringRelease</span>(script);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}<br />
<br />
<span style="color:#881350;">void</span> <span style="color:#003369;">JSValuePrint</span>(<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JSContextRef ctx,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JSValueRef value,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;JSValueRef *exception)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;JSStringRef string = <span style="color:#003369;">JSValueToStringCopy</span>(ctx, value, exception);<br />
&nbsp;&nbsp;&nbsp;&nbsp;size_t length = <span style="color:#003369;">JSStringGetLength</span>(string);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">char</span> *buffer = <span style="color:#003369;">malloc</span>(length+<span style="color:#0000ff;">1</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">JSStringGetUTF8CString</span>(string, buffer, length+<span style="color:#0000ff;">1</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">JSStringRelease</span>(string);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">puts</span>(buffer);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">free</span>(buffer);<br />
}</div>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/428471633" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/10/10/command-line-interpreter-and-repl-for-jscocoa/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/10/10/command-line-interpreter-and-repl-for-jscocoa/</feedburner:origLink></item>
		<item>
		<title>Recovering Censored Text Using Photoshop and JavaScript</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/428471634/</link>
		<comments>http://tlrobinson.net/blog/2008/10/08/recovering-censored-text-using-adobe-photoshop-cs3/#comments</comments>
		<pubDate>Wed, 08 Oct 2008 11:45:09 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Hacks]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[Programming]]></category>

		<category><![CDATA[Security]]></category>

		<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=52</guid>
		<description><![CDATA[My friend Andrew recently posted a teaser for a new project he&#8217;s working on, but with part of the headline pixelated to obscure what the project actually is. My curiosity got the best of me and I decided to do what any self-respecting geek would do: write a program to figure out what the censored [...]]]></description>
			<content:encoded><![CDATA[<p>My friend Andrew recently posted a <a href="http://tmblg.com/post/53529111/its-coming">teaser</a> for a new project he&#8217;s working on, but with part of the headline pixelated to obscure what the project actually is. My curiosity got the best of me and I decided to do what any self-respecting geek would do: write a program to figure out what the censored text said.</p>

<p>Ultimately I failed to recover most of the censored text (except &#8220;to&#8221;), so I had to cheat a little. The following video is the program running on a very similar image I created. This proves it works in ideal conditions, but needs some improvement to work in less than ideal cases.</p>

<p><object width="400" height="300">   <param name="allowfullscreen" value="true" />   <param name="allowscriptaccess" value="always" />   <param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=1913931&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1" />   <embed src="http://vimeo.com/moogaloop.swf?clip_id=1913931&amp;server=vimeo.com&amp;show_title=1&amp;show_byline=1&amp;show_portrait=0&amp;color=&amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="400" height="300"></embed></object><br /></p>

<p>(and no, as far as I know my friend&#8217;s project has nothing to do with eating monkeys)</p>

<p>Applying a filter like Photoshop&#8217;s &#8220;mosaic&#8221; filter obscures the original data, but doesn&#8217;t remove it entirely. If we can reconstruct an image with <em>known</em> text that looks very similar to original image, then we can be pretty sure the original text is the same as our known text. This is very similar in principle to brute-force cracking a password hash. For a more detailed explanation <a href="http://dheera.net/projects/blur.php">see this article</a>.</p>

<p>Photoshop was an obvious choice since I needed to recreate the exact same fancy styling as the original image, then apply the exact same mosaic filter. I figured I would have to write a script that tells Photoshop to generate images, then use an external tool to actually compare them to the original.</p>

<p>It turns out that Photoshop CS3 has all the features necessary to pull the whole thing off without any other programs or tools. The most important feature is the JavaScript scripting environment built into Photoshop, which is far more powerful than the AppleScript environment (and a <em>much</em> nicer language, in my opinion).</p>

<p>CS3 added two other features that are critical to this task: Smart Filters, and Measurements. Smart Filters lets you edit a layer (namely the text with effects applied) <em>after</em> you apply a filter that would have previously require rasterization. This lets us apply the censoring filter to our styled text, and later change the text without having to manually reapply the filter. The &#8220;measurements&#8221; feature lets you record various statistics about an image or portion of an image: in our case we&#8217;ll want the &#8220;average gray value&#8221; of the &#8220;difference&#8221; between the original and generated images.</p>

<p><a href='http://tlrobinson.net/blog/wp-content/uploads/2008/10/picture-10.png' title='picture-10.png'><img src='http://tlrobinson.net/blog/wp-content/uploads/2008/10/picture-10.png' alt='picture-10.png' /></a></p>

<p>First we need to prepare the environment. Open the original image in Photoshop, and attempt to replicate the original un-censored text as closely as possible (you need <em>some</em> uncensored text as a reference). Place your text layer on top of the original and toggle between normal and &#8220;difference&#8221; blending modes to see how you close you are. Ideally everything will be black in &#8220;difference&#8221; mode. It&#8217;s very important to precisely match the font, size, spacing, color, effects like drop shadows or outlines, and even the background. If these are off even by a little bit it will throw things off. I ended up having to cheat because I couldn&#8217;t match the slick styling of the original text with my lame Photoshop design skills.</p>

<p>Once the text matches and is lined up perfectly, select the layer then choose &#8220;Convert for Smart Filters&#8221; from the &#8220;Filter&#8221; menu. Now select the censored portion of the text and apply the same filter used on the original image, again matching it as closely as possible. For the mosaic filter, you can line up the &#8220;grid&#8221; by adjusting the origin and size of the selection (yeah, it&#8217;s a pain).</p>

<p><a href='http://tlrobinson.net/blog/wp-content/uploads/2008/10/picture-11.png' title='picture-11.png'><img src='http://tlrobinson.net/blog/wp-content/uploads/2008/10/picture-11.png' alt='picture-11.png' /></a></p>

<p>Finally, make sure your layer is on top of the original, and the blending mode on your layer is set to &#8220;difference&#8221;. Double-click the Smart Object layer to open it&#8217;s source document, and adjust the variables listed at the top of the JavaScript to match the names and layers. Also, in the menu &#8220;Analysis&#8221;: &#8220;Select Data Points&#8221;: &#8220;Custom&#8230;&#8221; make sure only &#8220;Gray Value (Mean)&#8221; is checked.</p>

<h3>Code</h3>

<p>Rather than attempting to explain it in detail here, just read the code and comments. Here&#8217;s a quick summary:</p>

<ol>
<li>Start with the first character. Try setting it to each of the possibilities (a through z, and a space), and record the difference score between the original image and generated image. Only look at the first half of the current character (since the second half will be influenced by the *next* character).</li>
<li>Sort the results. Lower scores are better (less different)</li>
<li>Now try each of the top 3 characters along with every possibility for the *next* character. This time record score for the whole width of the current character since we&#8217;re checking the next character as well.</li>
<li>Pick the best choice, either the best permutation out of all 81 combinations (3 best * 27 possible), or out of the 3 averages for each best.</li>
<li>Repeat for the next character until done.</li>
</ol>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;">// change these parameters based on document names and layer ordering<br />
</span>baseDocName = <span style="color:#760f15;">&quot;base.psd&quot;</span>;<br />
baseDocTextLayer = <span style="color:#0000ff;">0</span>;<br />
textDocName = <span style="color:#760f15;">&quot;The easy way to do somethingss12.psb&quot;</span>;<br />
textDocTextLayer = <span style="color:#0000ff;">0</span>;<br />
<br />
knownString = <span style="color:#760f15;">&quot;The easy way &quot;</span>; <span style="color:#236e25;">// the part of the string that&#8217;s already known<br />
</span>missingLength = <span style="color:#0000ff;">20</span>; <span style="color:#236e25;">// number of characters to figure out<br />
</span><br />
method = <span style="color:#0000ff;">3</span>;<br />
debug = <span style="color:#881350;">false</span>;<br />
<br />
<span style="color:#881350;">function</span> <span style="color:#003369;">main</span>()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;baseDoc = documents[baseDocName];<br />
&nbsp;&nbsp;&nbsp;&nbsp;textDoc = documents[textDocName];<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// get the top left corner of the text layer in the main doc<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> mainBounds = baseDoc.artLayers[baseDocTextLayer].bounds,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mainX = mainBounds[<span style="color:#0000ff;">0</span>].<span style="color:#003369;">as</span>(<span style="color:#760f15;">&quot;px&quot;</span>),<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mainY = mainBounds[<span style="color:#0000ff;">1</span>].<span style="color:#003369;">as</span>(<span style="color:#760f15;">&quot;px&quot;</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// possible characters include space and lowercase.<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> possibleCharacters = [<span style="color:#760f15;">&quot; &quot;</span>];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">var</span> i = <span style="color:#0000ff;">0</span>; i &lt; <span style="color:#0000ff;">26</span>; i++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;possibleCharacters.<span style="color:#003369;">push</span>(String.<span style="color:#003369;">fromCharCode</span>(<span style="color:#760f15;">&quot;a&quot;</span>.<span style="color:#003369;">charCodeAt</span>(<span style="color:#0000ff;">0</span>) + i));<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">//possibleCharacters.push(String.fromCharCode(&quot;A&quot;.charCodeAt(0) + i)); // uncomment for uppercase letters<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> fudgeFactor = <span style="color:#0000ff;">3</span>, &nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// number of top choices to try<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;guess = <span style="color:#760f15;">&quot;&quot;</span>; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// guessed letters so far<br />
</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">var</span> charNum = <span style="color:#0000ff;">0</span>; charNum &lt; missingLength; charNum++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results = [];<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// get the beginning and potential end (width of a &quot;M&quot;) of the next character<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> w1 = <span style="color:#003369;">getStringBounds</span>(knownString + guess),<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;w2 = <span style="color:#003369;">getStringBounds</span>(knownString + guess + <span style="color:#760f15;">&quot;M&quot;</span>);<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// PASS 1: half the potential width, since we&#8217;re not looking at the next character yet<br />
</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// half the width of &quot;M&quot;<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">setSelection</span>(mainX, mainY, (w1[<span style="color:#0000ff;">2</span>].<span style="color:#003369;">as</span>(<span style="color:#760f15;">&quot;px&quot;</span>) + w2[<span style="color:#0000ff;">2</span>].<span style="color:#003369;">as</span>(<span style="color:#760f15;">&quot;px&quot;</span>)) <span style="color:#760f15;">/ 2, 15);/</span>/w2[<span style="color:#0000ff;">3</span>].<span style="color:#003369;">as</span>(<span style="color:#760f15;">&quot;px&quot;</span>));<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// get the score for every letter<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">var</span> i = <span style="color:#0000ff;">0</span>; i &lt; possibleCharacters.length; i++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> val = <span style="color:#003369;">getStringScore</span>(knownString + guess + possibleCharacters[i])<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> res = { ch: possibleCharacters[i], v: val };<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results.<span style="color:#003369;">push</span>(res);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// sort from best (lowest) to worst score<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;results = results.<span style="color:#003369;">sort</span>(<span style="color:#881350;">function</span><span style="color:#003369;"> </span>(a,b) { <span style="color:#881350;">return</span> a.v - b.v; });<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// method 1: too simple, poor results<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(method == <span style="color:#0000ff;">1</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;guess += results[<span style="color:#0000ff;">0</span>].ch;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// PASS 2: full (potential) width of the current character, testing each of the few top matches and every possible next character<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// full width of &quot;M&quot;<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">setSelection</span>(mainX, mainY, w2[<span style="color:#0000ff;">2</span>].<span style="color:#003369;">as</span>(<span style="color:#760f15;">&quot;px&quot;</span>), <span style="color:#0000ff;">15</span>);<span style="color:#236e25;">//w2[3].as(&quot;px&quot;));<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> minValue = Number.MAX_VALUE,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;minChar = null,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;minSum = Number.MAX_VALUE,<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;minSumChar = null;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// try the few best from the first pass<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">var</span> i = <span style="color:#0000ff;">0</span>; i &lt; fudgeFactor; i++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> sum = <span style="color:#0000ff;">0</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">var</span> j = <span style="color:#0000ff;">0</span>; j &lt; possibleCharacters.length; j++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// get the score for the potential best PLUS each possible next character<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> val = <span style="color:#003369;">getStringScore</span>(knownString + guess + results[i].ch + possibleCharacters[j])<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;sum += val;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(val &lt; minValue)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;minValue = val;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;minChar = results[i].ch;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(sum &lt; minSum)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;minSum = sum;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;minSumChar = results[i].ch;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// if the results aren&#8217;t consistent let us know<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(debug &amp;&amp; results[<span style="color:#0000ff;">0</span>].ch != minSumChar || minChar != minSumChar)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">alert</span>(minChar + <span style="color:#760f15;">&quot;,&quot;</span> + minSumChar + <span style="color:#760f15;">&quot; (&quot;</span> +results[<span style="color:#0000ff;">0</span>].ch + <span style="color:#760f15;">&quot;,&quot;</span> + results[<span style="color:#0000ff;">1</span>].ch+ <span style="color:#760f15;">&quot;,&quot;</span> + results[<span style="color:#0000ff;">2</span>].ch+ <span style="color:#760f15;">&quot;)&quot;</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(method == <span style="color:#0000ff;">2</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// method 2: best of all permutations<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;guess += minChar;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">else</span><br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// method 3: best average <br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;guess += minSumChar;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">WaitForRedraw</span>();<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
}<br />
<br />
<span style="color:#236e25;">// measure the gray value mean in the current selection<br />
</span><span style="color:#881350;">function</span> <span style="color:#003369;">getMeasurement</span>()<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// delete existing measurements<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;app.measurementLog.<span style="color:#003369;">deleteMeasurements</span>();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// record new measurement<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument = baseDoc;<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument.<span style="color:#003369;">recordMeasurements</span>();<span style="color:#236e25;">//MeasurementSource.MEASURESELECTION, [&quot;GrayValueMean&quot;]);<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// export measurements to a file<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> f = <span style="color:#881350;">new</span> <span style="color:#003369;">File </span>(<span style="color:#760f15;">&quot;/tmp/crack-tmp-file.txt&quot;</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.measurementLog.<span style="color:#003369;">exportMeasurements</span>(f);<span style="color:#236e25;">//, MeasurementRange.ACTIVEMEASUREMENTS, [&quot;GrayValueMean&quot;]);<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// open the file, read, and parse<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;f.<span style="color:#003369;">open</span>();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> line = f.<span style="color:#003369;">read</span>();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> matches = line.<span style="color:#003369;">match</span>(<span style="color:#760f15;">/[0-9]+(\.[0-9]+)?/</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(matches)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> val = <span style="color:#003369;">parseFloat</span>(matches[<span style="color:#0000ff;">0</span>]);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> val;<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> null;<br />
}<br />
<br />
<span style="color:#236e25;">// sets the value of the test string<br />
</span><span style="color:#881350;">function</span> <span style="color:#003369;">setString</span>(string)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument = textDoc;<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument.artLayers[textDocTextLayer].textItem.contents = string;<br />
<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">WaitForRedraw</span>();<br />
}<br />
<br />
<span style="color:#236e25;">// gets the difference between the original and test strings in the currently selected area <br />
</span><span style="color:#881350;">function</span> <span style="color:#003369;">getStringScore</span>(string)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">setString</span>(string);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// save document to propagate changes parent of smart object<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument = textDoc;<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument.<span style="color:#003369;">save</span>();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// return the average gray value<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> <span style="color:#003369;">getMeasurement</span>();<br />
}<br />
<br />
<span style="color:#236e25;">// get the bounds of the text<br />
</span><span style="color:#881350;">function</span> <span style="color:#003369;">getStringBounds</span>(string)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument = textDoc;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// set the string of the text document<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">setString</span>(string);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// select top left pixel. change this if it&#8217;s not empty<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument.selection.<span style="color:#003369;">select</span>([[<span style="color:#0000ff;">0</span>,<span style="color:#0000ff;">0</span>], [<span style="color:#0000ff;">0</span>,<span style="color:#0000ff;">1</span>], [<span style="color:#0000ff;">1</span>,<span style="color:#0000ff;">1</span>], [<span style="color:#0000ff;">1</span>,<span style="color:#0000ff;">0</span>]]);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// select similar pixels (i.e. everything that&#8217;s not text)<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument.selection.<span style="color:#003369;">similar</span>(<span style="color:#0000ff;">1</span>, <span style="color:#881350;">false</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// invert selection to get just the text<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument.selection.<span style="color:#003369;">invert</span>();<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// return the bounds of the resulting selection<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">return</span> app.activeDocument.selection.bounds;<br />
}<br />
<br />
<span style="color:#236e25;">// sets the base document&#8217;s selection to the given rectange<br />
</span><span style="color:#881350;">function</span> <span style="color:#003369;">setSelection</span>(x, y, w, h)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument = baseDoc;<br />
&nbsp;&nbsp;&nbsp;&nbsp;app.activeDocument.selection.<span style="color:#003369;">select</span>([[x,y], [x,y+h], [x+w,y+h], [x+w,y]]);<br />
}<br />
<br />
<span style="color:#236e25;">// pauses for Photoshop to redraw. taken from reference docs.<br />
</span><span style="color:#881350;">function</span> <span style="color:#003369;">WaitForRedraw</span>() <br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#236e25;">// return; // uncomment for slight speed boost<br />
</span>&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> eventWait = <span style="color:#003369;">charIDToTypeID</span>(<span style="color:#760f15;">&quot;Wait&quot;</span>) <br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> enumRedrawComplete = <span style="color:#003369;">charIDToTypeID</span>(<span style="color:#760f15;">&quot;RdCm&quot;</span>) <br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> typeState = <span style="color:#003369;">charIDToTypeID</span>(<span style="color:#760f15;">&quot;Stte&quot;</span>) <br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> keyState = <span style="color:#003369;">charIDToTypeID</span>(<span style="color:#760f15;">&quot;Stte&quot;</span>) <br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> desc = <span style="color:#881350;">new</span> <span style="color:#003369;">ActionDescriptor</span>() <br />
&nbsp;&nbsp;&nbsp;&nbsp;desc.<span style="color:#003369;">putEnumerated</span>(keyState, typeState, enumRedrawComplete) <br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#003369;">executeAction</span>(eventWait, desc, DialogModes.NO) <br />
}<br />
<br />
<span style="color:#003369;">main</span>();<br />
</div>

<p>The raw code and sample Photoshop file are <a href="http://github.com/tlrobinson/tlrobinson/tree/master/recover/">available on GitHub</a>.</p>

<h3>Issues</h3>

<p>This problem is particularly tricky for proportional fonts, since if you get any character wrong and it&#8217;s width is different than the actual character, then all subsequent characters will be misaligned, causing more incorrect guesses, compounding the problem even more, and so on. I&#8217;m not sure how to deal with this, other than improving the overall matching quality. Ideally we would test every possible combination for the entire string, but that would require 27^n tests, where n is the number of unknown characters. This is obviously not feasible.</p>

<p>With the simplistic method of iterating over each position and trying each possible character, it turned out that almost every single &#8220;guess&#8221; was for the letters &#8220;m&#8221; or &#8220;w&#8221;. This was because for positions where the original was narrower characters, the &#8220;m&#8221; would &#8220;bleed&#8221; over into the <em>next</em> position, improving the score regardless of how well it actually matched the current character. To get around this, we only look at the difference for the first <em>half</em> of the character&#8217;s position.</p>

<p>Since looking at the first half of the character removes some valuable information, we then do a second pass using the top several guesses from the first pass, this time looking at the full width of the current character along with each of the possible next characters (27 tests + 3 runs times 27 tests results in 108 tests per character).</p>

<p>Further improvements could definitely be made, but I&#8217;ve already spent several hours too many on this.</p>

<p>The current algorithm runs at about 3 characters per minute. The overhead of Photoshop saving the Smart Object document on every individual test case is significant. If this were a special purpose program manipulating images directly it would likely be much faster. The tradeoff, of course, is you have all of Photoshop&#8217;s flexibility at your disposal for matching the original document&#8217;s font, size, style, spacing, and censoring effects, which is very important. For small amounts of text speed isn&#8217;t a problem.</p>

<h3>Conclusion</h3>

<p>While my original goal of recovering the censored text on my friend&#8217;s page was never achieved, the project was a success. It works well on my test image, and I learned about 3 obscure but cool and useful features of Photoshop!</p>

<p>Oh, and <em>that&#8217;s</em> why &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588; uses black ink to &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588; their &#x2588;&#x2588;&#x2588;&#x2588;&#x2588;&#x2588;!</p>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/428471634" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/10/08/recovering-censored-text-using-adobe-photoshop-cs3/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/10/08/recovering-censored-text-using-adobe-photoshop-cs3/</feedburner:origLink></item>
		<item>
		<title>git bisect run</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/428471635/</link>
		<comments>http://tlrobinson.net/blog/2008/10/01/git-bisect-run/#comments</comments>
		<pubDate>Wed, 01 Oct 2008 10:03:45 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Git]]></category>

		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=51</guid>
		<description><![CDATA[This feature of git is too cool not to blog about: git bisect, and more specifically, git bisect run.

&#8220;git bisect&#8221; is a tool that facilitates a binary search of changes to your git repository to help find where a bug was introduced. You can walk through the process manually using &#8220;git bisect {good,bad,skip}&#8220;, or if [...]]]></description>
			<content:encoded><![CDATA[<p>This feature of git is too cool not to blog about: <a href="http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html"><strong>git bisect</strong></a>, and more specifically, <strong>git bisect run</strong>.</p>

<p>&#8220;git bisect&#8221; is a tool that facilitates a binary search of changes to your git repository to help find where a bug was introduced. You can walk through the process manually using &#8220;git bisect <em>{good,bad,skip}</em>&#8220;, or if you can write a script that automates checking for the bug, you can use &#8220;git bisect run <em>scriptname</em>&#8221; to have git do all the work for you.</p>

<p>The script should return 0 if the bug does not exist, and some other number (except 125) if the bug does exist.</p>

<p>In my case, <a href="http://cappuccino.org">Cappuccino&#8217;s</a> &#8220;steam&#8221; build tool was failing, so I wrote a simple script that would test it by trying to run &#8220;steam&#8221; on Foundation:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#236e25;">#!/bin/sh<br />
</span><br />
<span style="color:#236e25;"># install a known working copy of the build tools<br />
</span><span style="color:#440088;">pushd</span> ../tmp/Tools<br />
<span style="color:#880088;">sudo</span> <span style="color:#880088;">sh</span> <span style="color:#880088;">install</span>-tools<br />
<span style="color:#440088;">popd</span><br />
<br />
<span style="color:#236e25;"># build whatever version of Cappuccino git bisect has checked out for us<br />
</span><span style="color:#880088;">rm</span> -rf <span style="color:#c4620a;">$STEAM_BUILD</span><br />
ant release<br />
<br />
<span style="color:#236e25;"># install the freshly built tools<br />
</span><span style="color:#440088;">pushd</span> <span style="color:#c4620a;">$STEAM_BUILD</span>/Cappuccino/Tools<br />
<span style="color:#880088;">sudo</span> <span style="color:#880088;">sh</span> <span style="color:#880088;">install</span>-tools<br />
<span style="color:#440088;">popd</span><br />
<br />
<span style="color:#236e25;"># run steam on Foundation to see if the built tools work<br />
</span><span style="color:#880088;">rm</span> -rf <span style="color:#c4620a;">$STEAM_BUILD</span><br />
<span style="color:#440088;">pushd</span> Foundation<br />
steam<br />
RETURN=$?<br />
<span style="color:#440088;">popd</span><br />
<br />
<span style="color:#236e25;"># return the recorded return value<br />
</span><span style="color:#440088;">exit</span> <span style="color:#c4620a;">$RETURN</span><br />
</div>

<p>I provided &#8220;git bisect start&#8221; with a known bad commit and a known good commit, then ran &#8220;git bisect run&#8221;:</p>

<pre><code>git bisect start c1e882ace1dd29aea98d9247db304fe5d5077df7 d6c0f8802a2fd3a07e14418de7744ae04ae4499e
git bisect run ../test.sh</code></pre>

<p>Sure enough, a few minutes later &#8220;git bisect&#8221; reported exactly which commit caused the problem:</p>

<pre><code>01177a7e0237b1bd026cf0c4ca923fced8536772 is first bad commit
commit 01177a7e0237b1bd026cf0c4ca923fced8536772
Author: name removed to protect the not-so-innocent

Date:   Tue Sep 30 17:29:56 2008 -0700

   Fix keys conflict in CPDictionary
   
   [#77 state:resolved]

:040000 040000 bf4ceafbf439aa54790fc57a2a78dec8283abadf b269f85f3f4ed4d08e4c8261ee409e9d88255b1d M    AppKit
:040000 040000 f9c1fdb3c02c7c899285c8cb8123b29e99a17e46 48cc5aefde707119fe956dd882dcd8f8182e7012 M    Foundation
:040000 040000 b780743d17ddb0ebda188703725e9dabf23fd458 c72750dabd99ca42d9144f3074c68c42b727d028 M    Objective-J
bisect run success</code></pre>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/428471635" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/10/01/git-bisect-run/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/10/01/git-bisect-run/</feedburner:origLink></item>
		<item>
		<title>YouTube Fullscreen Bookmarklet</title>
		<link>http://feeds.feedburner.com/~r/tlrobinson/~3/428471636/</link>
		<comments>http://tlrobinson.net/blog/2008/09/28/youtube-fullscreen-bookmarklet/#comments</comments>
		<pubDate>Sun, 28 Sep 2008 23:47:22 +0000</pubDate>
		<dc:creator>Tom</dc:creator>
		
		<category><![CDATA[Bookmarklet]]></category>

		<category><![CDATA[Hacks]]></category>

		<category><![CDATA[JavaScript]]></category>

		<category><![CDATA[Web]]></category>

		<guid isPermaLink="false">http://tlrobinson.net/blog/?p=50</guid>
		<description><![CDATA[I find it incredibly annoying when an embedded YouTube video can&#8217;t be made fullscreen, and I have to switch to YouTube.com just to watch it.

So, I wrote this simple little bookmarklet which modifies the embed code for any YouTube videos to allow fullscreen. It could also easily be made into a user script for GreaseMonkey, [...]]]></description>
			<content:encoded><![CDATA[<p>I find it incredibly annoying when an embedded YouTube video can&#8217;t be made fullscreen, and I have to switch to YouTube.com just to watch it.</p>

<p>So, I wrote this simple little bookmarklet which modifies the embed code for any YouTube videos to allow fullscreen. It could also easily be made into a user script for GreaseMonkey, etc, to perform the modifications automatically.</p>

<p><a href="javascript:var os = document.getElementsByTagName(&quot;object&quot;); for (var i = 0; i &lt; os.length; i++) { var o = os[i].cloneNode(true); o.innerHTML = &#x27;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&#x27; + o.innerHTML; for (var j = 0; j &lt; o.childNodes.length; j++) { if (o.childNodes[j].name == &quot;movie&quot;) o.childNodes[j].value += &quot;&amp;fs=1&quot;; else if (o.childNodes[j].nodeName.toUpperCase() == &quot;EMBED&quot;) { o.childNodes[j].src += &quot;&amp;fs=1&quot;; o.childNodes[j].setAttribute(&quot;allowfullscreen&quot;, &quot;true&quot;); } } os[i].parentNode.replaceChild(o, os[i]); }">Bookmarklet</a></p>

<p>Source:</p>

<div style="text-align:left;color:#000000; background-color:#ffffff; border:solid black 1px; padding:0.5em 1em 0.5em 1em; overflow:auto;font-size:small; font-family:monospace; "><span style="color:#881350;">var</span> os = document.<span style="color:#003369;">getElementsByTagName</span>(<span style="color:#760f15;">&quot;object&quot;</span>);<br />
<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">var</span> i = <span style="color:#0000ff;">0</span>; i &lt; os.length; i++)<br />
{<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">var</span> o = os[i].<span style="color:#003369;">cloneNode</span>(<span style="color:#881350;">true</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;o.innerHTML = <span style="color:#760f15;">&#8216;&lt;param name=&quot;allowFullScreen&quot; value=&quot;true&quot;&gt;&lt;/param&gt;&#8217;</span> + o.innerHTML;<br />
&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">for</span><span style="color:#003369;"> </span>(<span style="color:#881350;">var</span> j = <span style="color:#0000ff;">0</span>; j &lt; o.childNodes.length; j++)<br />
&nbsp;&nbsp;&nbsp;&nbsp;{<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">if</span><span style="color:#003369;"> </span>(o.childNodes[j].name == <span style="color:#760f15;">&quot;movie&quot;</span>)<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;o.childNodes[j].value += <span style="color:#760f15;">&quot;&amp;fs=1&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="color:#881350;">else</span> <span style="color:#881350;">if</span><span style="color:#003369;"> </span>(o.childNodes[j].nodeName.<span style="color:#003369;">toUpperCase</span>() == <span style="color:#760f15;">&quot;EMBED&quot;</span>) {<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;o.childNodes[j].src += <span style="color:#760f15;">&quot;&amp;fs=1&quot;</span>;<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;o.childNodes[j].<span style="color:#003369;">setAttribute</span>(<span style="color:#760f15;">&quot;allowfullscreen&quot;</span>, <span style="color:#760f15;">&quot;true&quot;</span>);<br />
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;}<br />
&nbsp;&nbsp;&nbsp;&nbsp;os[i].parentNode.<span style="color:#003369;">replaceChild</span>(o, os[i]);<br />
}<br />
</div>

<p>Try it out on <a href="http://tlrobinson.net/projects/watorworldx/">this page</a> (I&#8217;m too lazy to actually make it fullscreen).</p>

<p>Note that currently it doesn&#8217;t actually check to make sure it&#8217;s a YouTube video that it&#8217;s modifying, so it might stomp all over other types of embeds.</p>
<img src="http://feeds.feedburner.com/~r/tlrobinson/~4/428471636" height="1" width="1"/>]]></content:encoded>
			<wfw:commentRss>http://tlrobinson.net/blog/2008/09/28/youtube-fullscreen-bookmarklet/feed/</wfw:commentRss>
		<feedburner:origLink>http://tlrobinson.net/blog/2008/09/28/youtube-fullscreen-bookmarklet/</feedburner:origLink></item>
	</channel>
</rss><!-- Dynamic Page Served (once) in 47.189 seconds --><!-- Cached page served by WP-Cache -->
