Skip to content

Commit

Permalink
Add Prawn::View mixin for custom documents
Browse files Browse the repository at this point in the history
Squashed commit of the following:

commit 1a17128
Author: Gregory Brown <[email protected]>
Date:   Mon Aug 11 10:59:27 2014 -0400

    Add a manual entry for Prawn::View

commit a95fc34
Author: Gregory Brown <[email protected]>
Date:   Mon Aug 11 10:41:00 2014 -0400

    Add test coverage for Prawn::View

commit 443567c
Merge: ce5eedd 3818c4f
Author: Gregory Brown <[email protected]>
Date:   Mon Aug 11 10:08:49 2014 -0400

    Merge branch 'master' of github.com:prawnpdf/prawn into view

commit ce5eedd
Author: Gregory Brown <[email protected]>
Date:   Mon Aug 11 10:06:48 2014 -0400

    Add API documentation for Prawn::View

commit 0e90868
Merge: f7bdf09 12ccc2c
Author: Gregory Brown <[email protected]>
Date:   Mon Aug 11 09:32:25 2014 -0400

    Merge branch 'master' of github.com:prawnpdf/prawn into view

commit f7bdf09
Author: Gregory Brown <[email protected]>
Date:   Thu Aug 7 12:02:12 2014 -0400

    Bump to latest Ruby 2.1 build

commit db67e27
Author: Gregory Brown <[email protected]>
Date:   Thu Jul 31 07:01:22 2014 -0400

    Somewhat softer method_missing hook for Prawn::View

commit 06dec83
Author: Gregory Brown <[email protected]>
Date:   Wed Jul 30 19:37:44 2014 -0400

    Allow NoMethodError to bubble up.

commit afa0ef6
Author: Gregory Brown <[email protected]>
Date:   Wed Jul 30 08:41:48 2014 -0400

    Add missing encoding comment

commit 8ce3eaa
Author: Gregory Brown <[email protected]>
Date:   Wed Jul 30 08:33:52 2014 -0400

    Add Prawn::View
  • Loading branch information
practicingruby committed Aug 11, 2014
1 parent 3818c4f commit c3e6d27
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 4 deletions.
4 changes: 1 addition & 3 deletions lib/prawn.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,8 @@ def configuration(*args)
require_relative "prawn/repeater"
require_relative "prawn/outline"
require_relative "prawn/grid"

require_relative "prawn/view"
require_relative "prawn/image_handler"



Prawn.image_handler.register(Prawn::Images::PNG)
Prawn.image_handler.register(Prawn::Images::JPG)
91 changes: 91 additions & 0 deletions lib/prawn/view.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# encoding: UTF-8
#
# prawn/view.rb : Implements a mixin for Prawn's DSL
#
# This is free software. Please see the LICENSE and COPYING files for details.

module Prawn
# This mixin allows you to create modular Prawn code without the
# need to create subclasses of Prawn::Document.
#
# class Greeter
# include Prawn::View
#
# def initialize(name)
# @name = name
# end
#
# def say_hello
# text "Hello, #{@name}!"
# end
#
# def say_goodbye
# font("Courier") do
# text "Goodbye, #{@name}!"
# end
# end
# end
#
# greeter = Greeter.new("Gregory")
#
# greeter.say_hello
# greeter.say_goodbye
#
# greeter.save_as("greetings.pdf")
#
# The short story about why you should use this mixin rather than
# creating subclasses of +Prawn::Document+ is that it helps
# prevent accidental conflicts between your code and Prawn's
# code.
#
# Here's the slightly longer story...
#
# By using composition rather than inheritance under the hood, this
# mixin allows you to keep your state separate from +Prawn::Document+'s
# state, and also will prevent unexpected method name collisions due
# to late binding effects.
#
# This mixin is mostly meant for extending Prawn's functionality
# with your own additions, but you can also use it to replace or
# wrap existing Prawn methods. Calling +super+ will still work
# as expected, and alternatively you can explictly call
# +document.some_method+ to delegate to Prawn where needed.
module View
# @group Experimental API

# Lazily instantiates a +Prawn::Document+ object.
#
# You can also redefine this method in your own classes to use
# a custom document class.
def document
@document ||= Prawn::Document.new
end

# Delegates all unhandled calls to object returned by +document+ method.
# (which is an instance of Prawn::Document by default)
def method_missing(m, *a, &b)
return super unless document.respond_to?(m)

document.send(m, *a, &b)
end

# Syntactic sugar that uses +instance_eval+ under the hood to provide
# a block-based DSL.
#
# greeter.update do
# say_hello
# say_goodbye
# end
#
def update(&b)
instance_eval(&b)
end

# Syntatic sugar that calls +document.render_file+ under the hood.
#
# greeter.save_as("greetings.pdf")
def save_as(filename)
document.render_file(filename)
end
end
end
4 changes: 3 additions & 1 deletion manual/basic_concepts/basic_concepts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
p.example "other_cursor_helpers"
p.example "adding_pages"
p.example "measurement"
p.example "view", :eval_source => false, :full_source => true

p.intro do
prose("This chapter covers the minimum amount of functionality you'll need to start using Prawn.
Expand All @@ -27,7 +28,8 @@
"Where the origin for the document coordinates is. What are Bounding Boxes and how they interact with the origin",
"How the cursor behaves",
"How to start new pages",
"What the base unit for measurement and coordinates is and how to use other convenient measures"
"What the base unit for measurement and coordinates is and how to use other convenient measures",
"How to build custom view objects that use Prawn's DSL"
)
end
end
Expand Down
42 changes: 42 additions & 0 deletions manual/basic_concepts/view.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# encoding: UTF-8
#
# To create a custom class that extends Prawn's functionality,
# use the <code>Prawn::View</code> mixin. This approach is safer than creating
# subclasses of <code>Prawn::Document</code> while being just as convenient.
#
# By using this mixin, your state will be kept completely separate
# from <code>Prawn::Document</code>'s state, and you will avoid accidental method
# collisions within <code>Prawn::Document</code>.
#
# To build custom classes that make use of other custom classes,
# you can define a method named <code>document()</code> that returns
# any object that acts similar to a <code>Prawn::Document</code>
# object. <code>Prawn::View</code> will then direct all delegated
# calls to that object instead.

require_relative "../example_helper"

class Greeter
include Prawn::View

def initialize(name)
@name = name
end

def say_hello
text "Hello, #{@name}!"
end

def say_goodbye
font("Courier") do
text "Goodbye, #{@name}!"
end
end
end

greeter = Greeter.new("Gregory")

greeter.say_hello
greeter.say_goodbye

greeter.save_as("greetings.pdf")
43 changes: 43 additions & 0 deletions spec/view_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# encoding: utf-8

require_relative "spec_helper"

describe "Prawn::View" do
let(:view_object) { Object.new.tap { |o| o.extend(Prawn::View) } }

it "provides a Prawn::Document object by default" do
expect(view_object.document).to be_kind_of(Prawn::Document)
end

it "delegates unhandled methods to object returned by document method" do
doc = mock("Document")
view_object.stubs(:document => doc)

doc.expects(:some_delegated_method)

view_object.some_delegated_method
end

it "allows a block-like DSL via the update method" do
doc = mock("Document")
view_object.stubs(:document => doc)

doc.expects(:foo)
doc.expects(:bar)

view_object.update do
foo
bar
end
end

it "aliases save_as() to document.render_file()" do
doc = mock("Document")
doc.expects(:render_file)

view_object.stubs(:document => doc)

view_object.save_as("foo.pdf")
end
end

0 comments on commit c3e6d27

Please sign in to comment.