Skip to content

Commit

Permalink
added template support on #start_new_page taking advantage of ObjectS…
Browse files Browse the repository at this point in the history
…tore#import_page

added specs, documentation, examples and some refactoring in start_new_page
  • Loading branch information
jonathangreenberg committed Jan 14, 2011
1 parent 9a1cffe commit 05d637d
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 55 deletions.
Binary file added data/pdfs/multipage_template.pdf
Binary file not shown.
19 changes: 19 additions & 0 deletions examples/general/page_templates.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# encoding: utf-8
#
# This sample demonstrates the use of the :template option when using #start_new_page to add a
# new page. Only one page of the template is currently imported for the template and which page of
# the pdf template is used can be specified with the :template_page option which defaults to 1.

require "#{File.dirname(__FILE__)}/../example_helper.rb"

filename = "#{Prawn::BASEDIR}/data/pdfs/multipage_template.pdf"

Prawn::Document.generate("page_template.pdf") do
text "This is the first page and content is brand new", :size => 18, :align => :center
start_new_page(:template => filename, :template_page => 2)
move_down 20
text "Here is some content that has been added to the page template", :size => 18, :align => :center
start_new_page(:template => filename, :template_page => 3)
move_down 20
text "Here is content that has been added to page 3 of the template", :size => 18, :align => :center
end
3 changes: 2 additions & 1 deletion lib/prawn/core/object_store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,12 @@ def object_id_for_page(k)
# exist. page_num is 1 indexed, so 1 indicates the first page.
#
def import_page(filename, page_num)
@loaded_objects = {}
unless File.file?(filename)
raise ArgumentError, "#{filename} does not exist"
end

hash = PDF::Hash.new(filename)
hash = PDF::Reader::ObjectHash.new(filename)
ref = hash.page_references[page_num - 1]

ref.nil? ? nil : load_object_graph(hash, ref).identifier
Expand Down
1 change: 1 addition & 0 deletions lib/prawn/core/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def new_content_stream
end
@content = document.ref(:Length => 0)
dictionary.data[:Contents] << document.state.store[@content]
document.save_graphics_state
end

def dictionary
Expand Down
118 changes: 66 additions & 52 deletions lib/prawn/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,38 @@
require "prawn/document/graphics_state"

module Prawn

# The Prawn::Document class is how you start creating a PDF document.
#
# There are three basic ways you can instantiate PDF Documents in Prawn, they
#
# There are three basic ways you can instantiate PDF Documents in Prawn, they
# are through assignment, implicit block or explicit block. Below is an exmple
# of each type, each example does exactly the same thing, makes a PDF document
# with all the defaults and puts in the default font "Hello There" and then
# saves it to the current directory as "example.pdf"
#
#
# For example, assignment can be like this:
#
#
# pdf = Prawn::Document.new
# pdf.text "Hello There"
# pdf.render_file "example.pdf"
#
#
# Or you can do an implied block form:
#
#
# Prawn::Document.generate "example.pdf" do
# text "Hello There"
# end
#
#
# Or if you need to access a variable outside the scope of the block, the
# explicit block form:
#
#
# words = "Hello There"
# Prawn::Document.generate "example.pdf" do |pdf|
# pdf.text words
# end
#
# Usually, the block forms are used when you are simply creating a PDF document
# that you want to immediately save or render out.
#
#
# See the new and generate methods for further details on the above.
#
class Document
Expand All @@ -68,11 +68,11 @@ class Document
# Example:
#
# module MyFancyModule
#
#
# def party!
# text "It's a big party!"
# end
#
#
# end
#
# Prawn::Document.extensions << MyFancyModule
Expand Down Expand Up @@ -152,7 +152,7 @@ def self.generate(filename,options={},&block)
#
# Additionally, :page_size can be specified as a simple two value array giving
# the width and height of the document you need in PDF Points.
#
#
# Usage:
#
# # New document, US Letter paper, portrait orientation
Expand All @@ -167,9 +167,9 @@ def self.generate(filename,options={},&block)
# # New document, with background
# pdf = Prawn::Document.new(:background => "#{Prawn::BASEDIR}/data/images/pigs.jpg")
#
def initialize(options={},&block)
Prawn.verify_options [:page_size, :page_layout, :margin, :left_margin,
:right_margin, :top_margin, :bottom_margin, :skip_page_creation,
def initialize(options={},&block)
Prawn.verify_options [:page_size, :page_layout, :margin, :left_margin,
:right_margin, :top_margin, :bottom_margin, :skip_page_creation,
:compress, :skip_encoding, :background, :info,
:optimize_objects, :template], options

Expand Down Expand Up @@ -204,7 +204,7 @@ def initialize(options={},&block)
end

@bounding_box = @margin_box

if block
block.arity < 1 ? instance_eval(&block) : block[self]
end
Expand Down Expand Up @@ -233,14 +233,19 @@ def page
# pdf.start_new_page(:left_margin => 50, :right_margin => 50)
# pdf.start_new_page(:margin => 100)
#
# A template for a page can be specified by pointing to the path of and existing pdf.
# One can also specify which page of the template which defaults otherwise to 1.
#
# pdf.start_new_page(:template => multipage_template.pdf, :template_page => 2)
#
def start_new_page(options = {})
if last_page = state.page
last_page_size = last_page.size
last_page_layout = last_page.layout
last_page_margins = last_page.margins
end
page_options = {:size => options[:size] || last_page_size,

page_options = {:size => options[:size] || last_page_size,
:layout => options[:layout] || last_page_layout,
:margins => last_page_margins}
if last_page
Expand All @@ -249,25 +254,27 @@ def start_new_page(options = {})
new_graphic_state.color_space = {}
page_options.merge!(:graphic_state => new_graphic_state)
end
merge_template_options(page_options, options) if options[:template]

state.page = Prawn::Core::Page.new(self, page_options)

apply_margin_options(options)
state.page.new_content_stream if options[:template]
use_graphic_settings(options[:template])

use_graphic_settings

unless options[:orphan]
state.insert_page(state.page, @page_number)
@page_number += 1
canvas { image(@background, :at => bounds.top_left) } if @background

canvas { image(@background, :at => bounds.top_left) } if @background
@y = @bounding_box.absolute_top

float do
state.on_page_create_action(self)
end
end
end

end

# Returns the number of pages in the document
#
Expand All @@ -279,9 +286,9 @@ def start_new_page(options = {})
def page_count
state.page_count
end

# Re-opens the page with the given (1-based) page number so that you can
# draw on it.
# draw on it.
#
# See Prawn::Document#number_pages for a sample usage of this capability.
#
Expand All @@ -305,7 +312,7 @@ def cursor
end

# Moves to the specified y position in relative terms to the bottom margin.
#
#
def move_cursor_to(new_y)
self.y = new_y + bounds.absolute_bottom
end
Expand All @@ -319,9 +326,9 @@ def move_cursor_to(new_y)
# pdf.text "C"
# end
#
# pdf.text "B"
#
def float
# pdf.text "B"
#
def float
mask(:y) { yield }
end

Expand Down Expand Up @@ -358,30 +365,30 @@ def render_file(filename)
# Another important point about bounding boxes is that all x and y measurements
# within a bounding box code block are relative to the bottom left corner of the
# bounding box.
#
#
# For example:
#
#
# Prawn::Document.new do
# # In the default "margin box" of a Prawn document of 0.5in along each edge
#
#
# # Draw a border around the page (the manual way)
# stroke do
# line(bounds.bottom_left, bounds.bottom_right)
# line(bounds.bottom_right, bounds.top_right)
# line(bounds.top_right, bounds.top_left)
# line(bounds.top_left, bounds.bottom_left)
# end
#
#
# # Draw a border around the page (the easy way)
# stroke_bounds
# end
#
#
def bounds
@bounding_box
end

# Sets Document#bounds to the BoundingBox provided. See above for a brief
# description of what a bounding box is. This function is useful if you
# description of what a bounding box is. This function is useful if you
# really need to change the bounding box manually, but usually, just entering
# and exiting bounding box code blocks is good enough.
#
Expand All @@ -391,14 +398,14 @@ def bounds=(bounding_box)

# Moves up the document by n points relative to the current position inside
# the current bounding box.
#
#
def move_up(n)
self.y += n
end

# Moves down the document by n points relative to the current position inside
# the current bounding box.
#
#
def move_down(n)
self.y -= n
end
Expand Down Expand Up @@ -443,8 +450,8 @@ def pad(y)
yield
move_down(y)
end


# Indents the specified number of PDF points for the duration of the block
#
# pdf.text "some text"
Expand All @@ -457,7 +464,7 @@ def pad(y)
def indent(x, &block)
bounds.indent(x, &block)
end


def mask(*fields) # :nodoc:
# Stores the current state of the named attributes, executes the block, and
Expand Down Expand Up @@ -494,7 +501,7 @@ def @bounding_box.move_past_bottom
raise Prawn::Errors::CannotGroup if second_attempt
old_bounding_box.move_past_bottom
group(second_attempt=true) { yield }
end
end

success
end
Expand All @@ -512,7 +519,7 @@ def @bounding_box.move_past_bottom
# text "bai"
# start_new_page
# text "-- Hai again"
# number_pages "<page> in a total of <total>", [bounds.right - 50, 0]
# number_pages "<page> in a total of <total>", [bounds.right - 50, 0]
# end
#
def number_pages(string, position)
Expand All @@ -529,16 +536,23 @@ def number_pages(string, position)
def compression_enabled?
!!state.compress
end

private

def use_graphic_settings
set_fill_color unless current_fill_color == "000000"
set_stroke_color unless current_stroke_color == "000000"
write_line_width unless line_width == 1
write_stroke_cap_style unless cap_style == :butt
write_stroke_join_style unless join_style == :miter
write_stroke_dash if dashed?
def merge_template_options(page_options, options)
object_id = state.store.import_page(options[:template], options[:template_page] || 1)
page_options.merge!(:object_id => object_id )
end

# setting override_settings to true ensures that a new graphic state does not end up using
# previous settings especially from imported template streams
def use_graphic_settings(override_settings = false)
set_fill_color if current_fill_color != "000000" || override_settings
set_stroke_color if current_stroke_color != "000000" || override_settings
write_line_width if line_width != 1 || override_settings
write_stroke_cap_style if cap_style != :butt || override_settings
write_stroke_join_style if join_style != :miter || override_settings
write_stroke_dash if dashed? || override_settings
end

def generate_margin_box
Expand All @@ -559,7 +573,7 @@ def generate_margin_box
# when the bounding box exits.
@bounding_box = @margin_box if old_margin_box == @bounding_box
end

def apply_margin_options(options)
if options[:margin]
# Treat :margin as CSS shorthand with 1-4 values.
Expand Down
3 changes: 1 addition & 2 deletions lib/prawn/document/internals.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,7 @@ def fresh_content_streams(options={})
go_to_page i
state.page.new_content_stream
apply_margin_options(options)
use_graphic_settings
save_graphics_state
use_graphic_settings(options[:template])
end
end

Expand Down
Loading

0 comments on commit 05d637d

Please sign in to comment.