Nick Sieger: Tag pdf do what you love tag:blog.nicksieger.com,2005:Typo Typo 2007-08-31T18:30:26+00:00 Nick Sieger urn:uuid:3d364af1-4743-44e4-bbe9-248c750675ef 2006-05-26T03:32:00+00:00 2007-08-31T18:30:26+00:00 Rails is simpler than Office <p>Before my big blog drought at the beginning of the year, I had an entry queued up talking about some success I&#8217;d experienced with Rails. A lot in the Rails world has progressed since then, but I still think the story is worth documenting. Also, the code to generate a PDF of mailing labels may be useful to somebody out there.</p> <blockquote> <p>I&#8217;ve had some Rails success lately building a home-use mailing list manager/rolodex application. There are plenty of ways that such a list could be maintained without resorting to a full web application framework such as Rails, but what the heck! The mailing list started life as an MS Access database; after my work computer was re-imaged I no longer had &#8220;access to Access&#8221; so it had a temporary layover in an Excel spreadsheet. Within the past couple of months I had moved it to a MySQL database as a way to nurture my fledgling Rails efforts.</p> <p>Ok, so nothing real special so far, except that in order to print mailing labels (one of the primary reasons for keeping such a list) I&#8217;d have to export the names to a .csv file and do a mail merge with Word. Until the most recent mailing.</p> <p>On a Saturday night I had the brainstorm to use Austin Ziegler&#8217;s <a href="http://ruby-pdf.rubyforge.org/">PDF::Writer</a> library to create a printable PDF directly from the Rails app, thus skipping the need to go through the mail merge rigamarole. Only a couple of hours of effort later, I had my mother-in-law&#8217;s Christmas mailing list printed out! Anyone who has ever done a mail merge with Word knows that clicking a single link to create the printable versions of the mailing labels is a <em>huge</em> improvement in usability. And finally, no MS bits were harmed in the production of this mailing!</p> </blockquote> <p>My starting point in building the code to generate PDFs was <a href="http://wiki.rubyonrails.com/rails/pages/HowtoGeneratePDFs">this page in the Rails wiki</a>. I decided to use the method that describes installing an &#8220;rpdf&#8221; template handler. Nowadays, you may as well use <a href="http://rubyforge.org/projects/railspdfplugin/">Josh Charles&#8217; Rails PDF plugin</a>, but for posterity I&#8217;ve packaged up my effort as a <a href="http://svn.caldersphere.net/svn/main/plugins/pdfrender/">simple plugin</a> as well (install into an existing Rails application with <code>./script/plugin install http://svn.caldersphere.net/svn/main/plugins/pdfrender</code>).</p> <p>With the plugin in place, all that&#8217;s necessary is a controller method to set up the data for the view, and the view code itself. The controller is as straightforward as you&#8217;d expect:</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="keyword">class </span><span class="class">AddressController</span> <span class="punct">&lt;</span> <span class="constant">ApplicationController</span> <span class="comment"># ...</span> <span class="keyword">def </span><span class="method">pdf</span> <span class="attribute">@addresses</span> <span class="punct">=</span> <span class="constant">Address</span><span class="punct">.</span><span class="ident">find</span><span class="punct">(</span><span class="symbol">:all</span><span class="punct">,</span> <span class="symbol">:order</span> <span class="punct">=&gt;</span> <span class="punct">'</span><span class="string">last_name, first_name</span><span class="punct">')</span> <span class="ident">render</span> <span class="symbol">:layout</span> <span class="punct">=&gt;</span> <span class="constant">false</span> <span class="keyword">end</span> <span class="keyword">end</span></code></pre></div> <p>The view code is a little more hairy but with a little thought the dimensioning and layout code could easily be DRY&#8217;d out.</p> <div class="typocode"><pre><code class="typocode_ruby "><span class="constant">FONT</span> <span class="punct">=</span> <span class="punct">&quot;</span><span class="string">Times-Roman</span><span class="punct">&quot;</span> <span class="constant">FONT_SIZE</span> <span class="punct">=</span> <span class="number">12</span> <span class="constant">COLS</span> <span class="punct">=</span> <span class="number">3</span> <span class="constant">LABELS_PER_PAGE</span> <span class="punct">=</span> <span class="number">30</span> <span class="constant">LABELS_PER_COL</span> <span class="punct">=</span> <span class="number">10</span> <span class="comment"># margins: .5in top &amp; bottom, 0.19 in left and right</span> <span class="comment"># table column widths: 2.63in | 0.13in | 2.63in | 0.13in | 2.63in</span> <span class="comment"># table rows: 1in height</span> <span class="constant">MARG_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.19</span> <span class="constant">MARG_Y</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.5</span> <span class="constant">CELL_Y</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">1</span> <span class="constant">CELL_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">2.63</span> <span class="constant">COL_PAD_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.19</span> <span class="constant">COL1_X</span> <span class="punct">=</span> <span class="constant">MARG_X</span> <span class="constant">COL2_X</span> <span class="punct">=</span> <span class="constant">COL1_X</span> <span class="punct">+</span> <span class="constant">CELL_X</span> <span class="punct">+</span> <span class="constant">COL_PAD_X</span> <span class="constant">COL3_X</span> <span class="punct">=</span> <span class="constant">COL2_X</span> <span class="punct">+</span> <span class="constant">CELL_X</span> <span class="punct">+</span> <span class="constant">COL_PAD_X</span> <span class="constant">CELL_PAD_X</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.13</span> <span class="constant">CELL_PAD_Y</span> <span class="punct">=</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">in2pts</span> <span class="number">0.25</span> <span class="constant">CELL_LINE_Y</span> <span class="punct">=</span> <span class="constant">FONT_SIZE</span> <span class="punct">+</span> <span class="number">2</span> <span class="keyword">def </span><span class="method">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">)</span> <span class="punct">[</span><span class="constant">COL1_X</span><span class="punct">,</span> <span class="constant">COL2_X</span><span class="punct">,</span> <span class="constant">COL3_X</span><span class="punct">][</span><span class="ident">col</span><span class="punct">]</span> <span class="punct">+</span> <span class="constant">CELL_PAD_X</span> <span class="keyword">end</span> <span class="keyword">def </span><span class="method">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="ident">line</span><span class="punct">)</span> <span class="constant">MARG_Y</span> <span class="punct">+</span> <span class="punct">((</span><span class="constant">LABELS_PER_COL</span> <span class="punct">-</span> <span class="ident">row</span><span class="punct">)</span> <span class="punct">*</span> <span class="constant">CELL_Y</span><span class="punct">)</span> <span class="punct">-</span> <span class="constant">CELL_PAD_Y</span> <span class="punct">-</span> <span class="punct">(</span><span class="ident">line</span> <span class="punct">*</span> <span class="constant">CELL_LINE_Y</span><span class="punct">)</span> <span class="keyword">end</span> <span class="keyword">def </span><span class="method">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="ident">col</span><span class="punct">,</span> <span class="ident">addr</span><span class="punct">,</span> <span class="ident">pdf</span><span class="punct">)</span> <span class="keyword">if</span> <span class="ident">addr</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">add_text_wrap</span><span class="punct">(</span><span class="ident">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">),</span> <span class="ident">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">0</span><span class="punct">),</span> <span class="constant">CELL_X</span><span class="punct">,</span> <span class="ident">addr</span><span class="punct">.</span><span class="ident">name</span><span class="punct">,</span> <span class="constant">FONT_SIZE</span><span class="punct">)</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">add_text_wrap</span><span class="punct">(</span><span class="ident">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">),</span> <span class="ident">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">1</span><span class="punct">),</span> <span class="constant">CELL_X</span><span class="punct">,</span> <span class="ident">addr</span><span class="punct">.</span><span class="ident">address</span><span class="punct">,</span> <span class="constant">FONT_SIZE</span><span class="punct">)</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">add_text_wrap</span><span class="punct">(</span><span class="ident">cell_x</span><span class="punct">(</span><span class="ident">col</span><span class="punct">),</span> <span class="ident">cell_y</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">2</span><span class="punct">),</span> <span class="constant">CELL_X</span><span class="punct">,</span> <span class="punct">&quot;</span><span class="string"><span class="expr">#{addr.city}</span>, <span class="expr">#{addr.state}</span> <span class="expr">#{addr.zip}</span></span><span class="punct">&quot;,</span> <span class="constant">FONT_SIZE</span><span class="punct">)</span> <span class="keyword">end</span> <span class="keyword">end</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">select_font</span><span class="punct">(</span><span class="constant">FONT</span><span class="punct">)</span> <span class="ident">pages</span> <span class="punct">=</span> <span class="attribute">@addresses</span><span class="punct">.</span><span class="ident">length</span> <span class="punct">/</span> <span class="constant">LABELS_PER_PAGE</span> <span class="ident">pages</span> <span class="punct">+=</span> <span class="number">1</span> <span class="keyword">if</span> <span class="punct">(</span><span class="attribute">@addresses</span><span class="punct">.</span><span class="ident">length</span> <span class="punct">%</span> <span class="constant">LABELS_PER_PAGE</span><span class="punct">)</span> <span class="punct">&gt;</span> <span class="number">0</span> <span class="number">0</span><span class="punct">.</span><span class="ident">upto</span><span class="punct">(</span><span class="ident">pages</span> <span class="punct">-</span> <span class="number">1</span><span class="punct">)</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">page</span><span class="punct">|</span> <span class="ident">start</span> <span class="punct">=</span> <span class="ident">page</span> <span class="punct">*</span> <span class="constant">LABELS_PER_PAGE</span> <span class="ident">address_page</span> <span class="punct">=</span> <span class="attribute">@addresses</span><span class="punct">[</span><span class="ident">start</span><span class="punct">..</span><span class="ident">start</span><span class="punct">+</span><span class="constant">LABELS_PER_PAGE</span><span class="punct">]</span> <span class="number">0</span><span class="punct">.</span><span class="ident">upto</span><span class="punct">(</span><span class="constant">LABELS_PER_COL</span> <span class="punct">-</span> <span class="number">1</span><span class="punct">)</span> <span class="keyword">do</span> <span class="punct">|</span><span class="ident">row</span><span class="punct">|</span> <span class="ident">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">0</span><span class="punct">,</span> <span class="ident">address_page</span><span class="punct">[</span><span class="ident">row</span><span class="punct">*</span><span class="constant">COLS</span><span class="punct">],</span> <span class="ident">pdf</span><span class="punct">)</span> <span class="ident">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">1</span><span class="punct">,</span> <span class="ident">address_page</span><span class="punct">[</span><span class="ident">row</span><span class="punct">*</span><span class="constant">COLS</span><span class="punct">+</span><span class="number">1</span><span class="punct">],</span> <span class="ident">pdf</span><span class="punct">)</span> <span class="ident">add_label</span><span class="punct">(</span><span class="ident">row</span><span class="punct">,</span> <span class="number">2</span><span class="punct">,</span> <span class="ident">address_page</span><span class="punct">[</span><span class="ident">row</span><span class="punct">*</span><span class="constant">COLS</span><span class="punct">+</span><span class="number">2</span><span class="punct">],</span> <span class="ident">pdf</span><span class="punct">)</span> <span class="keyword">end</span> <span class="ident">pdf</span><span class="punct">.</span><span class="ident">new_page</span> <span class="keyword">unless</span> <span class="ident">page</span> <span class="punct">+</span> <span class="number">1</span> <span class="punct">==</span> <span class="ident">pages</span> <span class="keyword">end</span></code></pre></div> <p>And that&#8217;s it! Avery labels in Rails!</p>