Section 15.5. Conclusion


15.4. Creating PDF Documents with PDF::Writer

PDF::Writer is a library to create PDF documents in Ruby. It can be installed as a RubyGem or downloaded from RubyForge. Basic document creation is pretty simple:

require 'rubygems' require 'pdf/writer' pdf = PDF::Writer.new


15.4.1. Basic Concepts and Techniques

One of the primary issues facing any document designer is that of text fonts (or type-faces). PDF::Writer supports five basic fonts; the first three have bold and italic (or oblique) variants.

  • Times-Roman

  • Helvetica

  • Courier

  • ZapfDingbats

  • Symbol

If no font is selected, Helvetica will be used by default. When selecting a font, a character substitution table can be created, allowing normally nonprintable characters or code pages to be simulated. There are 315 printable characters in Times-Roman, Helvetica, and Courier (149 have preassigned byte values); 190 printable characters in Symbol (189 have preassigned byte values); and 202 printable (and assigned) characters in ZapfDingbats. The fonts are encoded with an Adobe encoding, but individual characters may be reassigned during font selection.

It is not currently possible to print all 315 characters that are defined in a font file because after a font is selected, a new character substitution table cannot be provided. This should be fixed in a future version of PDF::Writer.

Here, we set the font for the PDF document we are creating to Times-Roman. The PDF reader will translate our text, which is assumed to be in WinAnsiEncoding, but it will substitute character 0x01 with the glyph called lozenge. We will see this character used later (in Listing 15.11).

pdf.select_font "Times-Roman",                 { :encoding => "WinAnsiEncoding",                   :differences  => {0x01 => "lozenge"}                 }


PDF::Writer sports facilities to automatically format text and create tables that are well documented. What may not be as clear is that, as long as none of the automatic pagination is triggered, PDF::Writer can be used to manually format pages in interesting ways. With judicious axis translation and scaling, we can draw four pages of content on a single page.

Under the current version of PDF::Writer (1.1.3), each of these "pages" must exactly fit one page. If the library's automatic pagination kicks in, a new physical page will be created. An enhanced version of this technique will be added to a future version PDF::Writer to work similarly to multicolumn support.

To demonstrate this technique, we'll create a method quadrant (shown in Listing 15.10). This will be incorporated into the lengthy example in the upcoming section. (The purpose is twofold: It shows how to produce a 4-up document, and it allows us to display four PDF pages on a single page of this book, as a space-saving measure.)

Listing 15.10. The quadrant Method

def quadrant(pdf, quad)   raise unless block_given?   mx = pdf.absolute_x_middle   my = pdf.absolute_y_middle   pdf.save_state   case quad   when :ul     pdf.translate_axis(0, my)   when :ur     pdf.translate_axis(mx, my)   when :ll     nil # pdf.translate_axis(0, 0)   when :lr     pdf.translate_axis(mx, 0)   end   pdf.scale_axis(0.5, 0.5)   pdf.y = pdf.page_height   yield   pdf.restore_state end

In this method, we force each page's construction to be completely contained within a block; in this way, we can transparently manage the drawing scale and axis without the wrapped page construction code knowing anything about it. The very first thing we do, though, is save the current state. This will save us from having to manually reset the axis scale and origin when we've finished. To prepare, we must move the origin point of the quadrant to the appropriate location on the drawing page (this is pdf.translate_axis x, y).

Suppose I move the origin point from (0, 0) to (50, 50). A line from (15, 20) to (35, 40) will be effectively drawn from (65, 70) to (85, 90). But the code that draws the line does not need to know this.

After translating the axis (moving the origin), the axis will be resized, effectively changing the size of the ruler that will be used. To achieve a quadrant effect, the axis is being scaled by half on the X scale and half on the Y scale (pdf.scale_axis 0.5, 0.5). This means that if I were now to draw my line from (0, 0) to (90, 90), it would effectively draw only from (0, 0) to (45, 45) without a translated axis, and from (90, 90) to (135, 135) with the translated axis. The code that drew the line still drew a 90-unit diagonal line. It just happens because of the rescaling that the units are half as big as they used to be.

We then yield to the block; after the block has finished, we restore the state using a built-in feature. If we did not do this, we would need code similar to the following to restore the axis scale and origin. The scale would need to be doubled, and the origin would need to be moved by the negative value of the original amount that it had been moved.

15.4.2. An Example Document

To demonstrate the techniques we just discussed, four different pages will be created and drawn within individual quadrant blocks. Three of these are minor variants of demonstration programs provided with PDF::Writer:

  • demo.rb, quadrant 1

  • individual-i.rb, quadrant 3

  • gettysburg.rb, quadrant 4

The fourth (quadrant 2) is also a variant but does not have a direct analogue to any demonstration program; its closest variant is the program chunkybacon.rb.

The entire code is in Listing 15.11, and the resulting output (a 4-up document page) is shown in Figure 15.4. This is a long listing; we will examine it in detail in the upcoming discussion.

Listing 15.11. Code for the Example Document

require 'rubygems' require 'pdf/writer' def quadrant(pdf, quad)   raise unless block_given?   mx = pdf.absolute_x_middle   my = pdf.absolute_y_middle   pdf.save_state   case quad   when :ul     pdf.translate_axis 0, my   when :ur     pdf.translate_axis mx, my   when :ll     nil # no translation needed   when :lr     pdf.translate_axis mx, 0   end   pdf.scale_axis(0.5, 0.5)   pdf.y = pdf.page_height   yield   pdf.restore_state end pdf = PDF::Writer.new pdf.select_font("Times-Roman",                  :encoding    => "WinAnsiEncoding",                  :differences => { 0x01 => "lozenge" }) mx = pdf.absolute_x_middle my = pdf.absolute_y_middle pdf.line(0, my, pdf.page_width, my).stroke pdf.line(mx, 0, mx, pdf.page_height).stroke # Top-Left: Demo (UL) quadrant(pdf, :ul) do   x  = pdf.absolute_right_margin   r1 = 25   40.step(1, -3) do |xw|     tone = 1.0 - (xw / 40.0) * 0.2     pdf.stroke_style(PDF::Writer::StrokeStyle.new(xw))     pdf.stroke_color(Color::RGB.from_fraction(1, tone, tone))     pdf.line(x, pdf.bottom_margin, x,              pdf.absolute_top_margin).stroke     x -= xw+2   end   40.step(1, -3) do |xw|     tone = 1.0 - (xw / 40.0) * 0.2     pdf.stroke_style(PDF::Writer::StrokeStyle.new(xw))     pdf.stroke_color(Color::RGB.from_fraction(1, tone, tone))     pdf.circle_at(pdf.left_margin + 10, pdf.margin_height - 15,                   r1).stroke     r1 += xw   end   pdf.stroke_color(Color::RGB::Black)   x = pdf.absolute_left_margin   y = pdf.absolute_bottom_margin   w = pdf.margin_width   h = pdf.margin_height   pdf.rectangle(x, y, w, h).stroke   text = "The Ruby Way"   y = pdf.absolute_top_margin   50.step(5, -5) do |size|     height = pdf.font_height(size)     y -= height     pdf.add_text(pdf.left_margin + 10, y, text, size)   end   (0...360).step(20) do |angle|     pdf.fill_color(Color::RGB.from_fraction(rand, rand, rand))     pdf.add_text(300 + Math.cos(PDF::Math.deg2rad(angle)) * 40,                  300 + Math.sin(PDF::Math.deg2rad(angle)) * 40,                  text, 20, angle)   end end pdf.fill_color Color::RGB::Black # Top-Right: Grampian Highlands (UR) quadrant(pdf, :ur) do   pdf.image("grampian-highlands.jpg",              :height => pdf.margin_height,              :resize => :width)   pdf.text("The Grampian Highlands, Scotland",             :justification => :center,             :font_size => 36)   pdf.text("\001August 2001\001", :justification => :center,                                   :font_size => 24)   pdf.move_pointer(24)   info = <<-'EOS'.split($/).join(" ").squeeze(" ") This picture was taken during a driving vacation through the Scottish highlands in August 2001 by Austin Ziegler.   EOS   pdf.text(info, :justification => :full, :font_size => 16,                  :left => 100, :right => 100) end pdf.fill_color Color::RGB::Black # Bottom-Left: Individual-I (LL) quadrant(pdf, :ll) do   require 'color/palette/monocontrast'   class IndividualI     def initialize(size = 100)       @size = size     end     # The size of the "i" in points.     attr_accessor :size     def half_i(pdf)       pdf.move_to(0, 82)       pdf.line_to(0, 78)       pdf.line_to(9, 78)       pdf.line_to(9, 28)       pdf.line_to(0, 28)       pdf.line_to(0, 23)       pdf.line_to(18, 23)       pdf.line_to(18, 82)       pdf.fill     end     private :half_i     def draw(pdf, x, y)       pdf.save_state       pdf.translate_axis(x, y)       pdf.scale_axis(1 * (@size / 100.0), -1 * (@size / 100.0))       pdf.circle_at(20, 10, 7.5)       pdf.fill       half_i(pdf)       pdf.translate_axis(40, 0)       pdf.scale_axis(-1, 1)       half_i(pdf)       pdf.restore_state     end   end   ii  = IndividualI.new(24)   x   = pdf.absolute_left_margin   y   = pdf.absolute_top_margin   bg  = Color::RGB.from_fraction(rand, rand, rand)   fg  = Color::RGB.from_fraction(rand, rand, rand)   pal = Color::Palette::MonoContrast.new(bg, fg)   sz  = 24   (-5..5).each do |col|     pdf.fill_color pal.background[col]     ii.draw(pdf, x, y)     ii.size += sz     x += sz / 2.0     y -= sz / 2.0     pdf.fill_color pal.foreground[col]     ii.draw(pdf, x, y)     x += sz / 2.0     y -= sz / 2.0     ii.size += sz   end end pdf.fill_color Color::RGB::Black # Bottom-Right: Gettysburg Address (LR) quadrant(pdf, :lr) do   pdf.text("The Gettysburg Address\n\n",             :font_size => 36, :justification => :center)   y0 = pdf.y + 18   speech = <<-'EOS'.split($/).join(" ").squeeze(" ") Four score and seven years ago our fathers brought forth on this continent a new nation, conceived in liberty and dedicated to the proposition that all men are created equal. Now we are engaged in a great civil war, testing whether that nation or any nation so conceived and so dedicated can long endure. We are met on a great battlefield of that war. We have come to dedicate a portion of that field as a final resting-place for those who here gave their lives that that nation might live. It is altogether fitting and proper that we should do this. But in a larger sense, we cannot dedicate, we cannot consecrate, we cannot hallow this ground. The brave men, living and dead who struggled here have consecrated it far above our poor power to add or detract. The world will little note nor long remember what we say here, but it can never forget what they did here. It is for us the living rather to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced. It is rather for us to be here dedicated to the great task remaining before us that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion that we here highly resolve that these dead shall not have died in vain, that this nation under God shall have a new birth of freedom, and that government of the people, by the people, for the people shall not perish from the earth. EOS   pdf.text(speech, :justification => :full, :font_size => 14,                    :left => 50, :right => 50)   pdf.move_pointer(36)   pdf.text("U.S. President Abraham Lincoln, 19 November 1863",             :justification => :right, :right => 100)   pdf.text("Gettysburg, Pennsylvania", :justification => :right,             :right => 100)   pdf.rounded_rectangle(pdf.left_margin + 25, y0, pdf.margin_width - 50,     y0 - pdf.y + 18, 10).stroke end pdf.save_as("4page.pdf")

Figure 15.4. Example document output (4-up).


To summarize, the four quadrants are as follows:

  • Upper-left quadrant, demo.rb

  • Upper-right quadrant, the Grampian Highlands photo

  • Lower-left quadrant, Individual I (individual-i.rb)

  • Lower-right quadrant: The Gettysburg Address

Let's refer to the four pieces of the final page by abbreviating the quadrant name as UL, UR, LL, and LR. The code uses the corresponding symbol names (:ul and so on).

The first quadrant (UL) is filled with vertical lines that progressively shrink from 40 units wide in progressively lighter shades. This is followed by circles that increase in radius as they reduce in line thickness, and the shades lighten. Finally, two sets of text are drawn: one in decreasing font sizes down the circles, and one with the text rotating around a central axis just off the edge of the vertical lines.

In the second quadrant (UR), we insert an image into the page and describe it. Of particular interest, note the date line. We are inserting byte 0x01 into the output stream here; when rendered, a lozenge character (diamond) will be substituted, as defined in our substitution table when we selected our font.

In the third quadrant (LL), the Individual-I demonstration program further demonstrates axis translation and scaling. The most interesting feature demonstrated here is axis inversion. When an axis is scaled by a negative value, the writing and drawing commands are inverted from the original direction. That means that in drawing the body of the "I", it's only necessary to define the drawing rules for half of the body; we can invert the X axis with pdf.scale_axis(-1, 1).

This final quadrant (LR) is relatively simple. The text of President Lincoln's speech at Gettysburg is formatted and enclosed in a box with rounded corners.

Saving the PDF at this point is simplicity itself. If we want to save it to disk, we simply use the save_as method on the PDF object:

pdf.save_as("4page.pdf")


It's also easy to send the PDF to a web browser from a CGI program:

require 'cgi' cgi = CGI.new out = pdf.render puts <<-EOS Content-Type: application/pdf Content-Disposition: inline; filename="4page.pdf" Size: #{out.size} EOS


Of course, this section has touched on only a fraction of the functionality of PDF::Writer. For more information, consult the online documentation. If you are familiar with the PDF format, be aware that PDF::Writer is not yet fully mature and does not support all features of the PDF specification.




The Ruby Way(c) Solutions and Techniques in Ruby Programming
The Ruby Way, Second Edition: Solutions and Techniques in Ruby Programming (2nd Edition)
ISBN: 0672328844
EAN: 2147483647
Year: 2004
Pages: 269
Authors: Hal Fulton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net