Generating PDF Files

Problem

You want to create a text or graphical document as a PDF, where you have complete control over the layout.

Solution

Use Austin Zeiglers PDF::Writer library, available as the pdf-writer gem. Its API gives you fine-grained control over the placement of text, images, and shapes.

This code uses PDF::Writer to produce a simple flyer with an image and a border (Figure 12-7). It assumes youve got a graphic called sue.png to insert into the document:

Figure 12-7. The flyer


	require 
ubygems
	require  
pdf/writer # => false

	# Putting "false" on the next line suppresses a huge output dump when
	# you run this code in irb.
	 
pdf = PDF::Writer.new; false

	pdf.text("LOST
DINOSAUR", :justification => :center, :font_size => 42,
	 :left => 50, :right => 50)
	pdf.image("sue.png", :left=> 100, :justification => :center, :resize => 0.75)
	pdf.text(%{Three-year-old Tyrannosaurus rex
Spayed
Responds to "Sue"},
	 :left => 80, :font_size => 20, :justification => :left)
	pdf.text("(555) 010-7829", :justification => :center, :font_size => 36)

	pdf.rectangle(pdf.left_margin + 25, pdf.y-25,
	 pdf.margin_width-50, pdf.margin_height-pdf.y+50).stroke; false

	pdf.save_as(flyer.pdf)

Discussion

So long as you e only calling Writer#text and Writer#image, PDF generation is easy. PDF automatically adds new text and images to the bottom of the current text, creating new pages as needed.

It gets tricky when you want to do something more complex, like draw shapes. Then you need to specify the placement and dimensions in coordinates.

Take as an example the Writer#rectangle call in the Solution:

	pdf.rectangle(pdf.left_margin, pdf.y-25,
	 pdf.margin_width, pdf.margin_height-pdf.y+25).stroke

The first two arguments are coordinates: the left edge of the rectangle and the bottom edge of the rectangle. The second two arguments are the width and height of the rectangle.

The width is simple enough: my box starts at the left margin and its width is pdf.margin_width user space units.[6] That is, my box takes up the entire width of the page except for the margin. The height is a little more tricky, because I do my own margins (25 user space units above and below the text), and because PDF coordinates start from the bottom-left of the page, not the top-left. Think of a Cartesian plane: the point (0,0)is below the point (0,1)and left of the point (1,0). Thats how it is on a PDF page.

[6] A PDF user space unit is 1/72 of an inch.

Writer#y gives you the current position of the PDF::Writer "cursor:" the y-coordinate of the space directly under the most recently added text or image. I use this to place the bottom of the box just under the text.

If you want to generate many PDF documents from a template, you don need to generate the whole document from scratch each time. You can create a PDF::Writer containing the skeleton of a document (say, just the corporate letterhead), then use Marshal.dump to save it to a binary string. You can then use Marshal.load as many times as necessary to get new documents, and fill in the blanks separately for each document.[7]

[7] Yes, this is kind of hacky. The best we can say is that the author of PDF::Writer himself recommends it (see "Creating Printable Documents with Ruby," cited in the following See Also section).

Heres a Ruby class that generates personalized certificates of achievement. We generate the PDF ahead of time with generate_pdf, leaving a blank space for the name. We can then fill in names by calling award_to. Instead of rerunning the PDF generation code every time, award_to copies the predefined PDF over and over again by loading it from its marshalled format.

	require 
ubygems
	require pdf/writer

	class Certificate

	 def initialize(achievement)
	 @without_name = Marshal.dump(generate_pdf(achievement))
	 end

	 def award_to(name)
	 pdf = Marshal.load(@without_name)
	 pdf.move_pointer(-225)
	 pdf.text("#{name}", :font_size => 64,
	 :justification => :center)
	 return pdf
	 end

	 private
	 def generate_pdf(achievement)
	 pdf = PDF::Writer.new( :orientation => :landscape )
	 pdf.info.title = "Certificate of Achievement"
	 draw_border(pdf, 10, 12, 16, 18)
	 draw_text(pdf, achievement)
	 return pdf
	 end

	 def draw_border(pdf, *px_pos)
	 px_pos.each do |px|
	 pdf.rectangle(px, px, pdf.page_width - (px * 2),
	 pdf.page_height - (px * 2)).stroke
	 end
	 end

	 def draw_text(pdf, achievement)
	 pdf.select_font "Times-Roman"
	 
pdf.text("
", :font_size => 52)
	 
pdf.text("Certificate of Achievement
", :justification => :center)
	 pdf.text("
", :font_size => 18)
	 pdf.text("hereby granted to
", :justification => :center)
	 pdf.text("

", :font_size => 64)
	 pdf.text("in recognition of achieving the status of",
	 :font_size => 18, :justification => :center)
	 pdf.text(achievement, :font_size => 64, :justification => :center)
	 end
	end

Now we can create a certificate and award it to many different people:

	certificate = Certificate.new(Ruby Hacker); false
	[Tricia Ball, Marty Wise, Dung Nguyen].each do |name|
	 certificate.award_to(name).save_as("#{name}.pdf")
	end

Figure 12-8 shows what tricia Ball.pdf looks like.

Figure 12-8. Congratulations!


This recipe only scratches the surface of what you can do with the PDF::Writer library. Fortunately, theres an excellent manual and RDoc documentation. Although the library provides a lot of classes, most of the methods you want will be in PDF::Writer and the mixin PDF::Writer::Graphics.

See Also

  • The PDF::Writer homepage (http://ruby-pdf.rubyforge.org/pdf-writer/ )
  • Generated RDoc (http://ruby-pdf.rubyforge.org/pdf-writer/doc/index.html)
  • "Creating Printable Documents with Ruby," published in artimas Ruby Code & Style, provides a helpful overview of the library as well as many links to PDF releated resources (http://www.artima.com/rubycs/articles/pdf_writerP.html)
  • The pdf-writer gem includes the source for the manual (manual.pwd)and a script (bin/techbook)that turns it into PDF format; the manual is also available online (http://ruby-pdf.rubyforge.org/pdf-writer/manual/index.html)
  • If you want to read a PDF file and extract its text, try Hannes Wysss rpdf2txt library (http://raa.ruby-lang.org/project/rpdf2txt/)
  • Recipe 8.16 for more about the Marshal technique for copying an object
  • The Certificate class is used again in Recipe 14.19, "Running Servlets with WEBrick"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



Ruby Cookbook
Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399

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