Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of github.com:sandal/prawn into chunky_graphics

  • Loading branch information...
commit d6a95e09af555207f39d193537e9b976aab447e7 2 parents 4add84b + e5947d0
@practicingruby authored
View
3  .gitignore
@@ -1,3 +1,4 @@
.*.sw?
nbproject
-pkg
+pkg
+.rvmrc
View
2  .rvmrc
@@ -0,0 +1,2 @@
+rvm gemset use prawn
+
View
2  examples/example_helper.rb
@@ -1,4 +1,6 @@
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+require 'rubygems'
require 'prawn'
+
Prawn.debug = true
View
48 examples/general/outlines.rb
@@ -13,38 +13,54 @@
text "Page 3. This is the second Chapter. It has a subsection. "
start_new_page
text "Page 4. More in the second Chapter. "
- define_outline do
- section 'Chapter 1', :page => 1, :closed => true do
- page 1, :title => 'Page 1'
- page 2, :title => 'Page 2'
+ outline.section 'Preface' do
+ outline.page :title => 'Preface'
+ end
+ outline.define do
+ section 'Chapter 1', :destination => 1, :closed => true do
+ page :destination => 1, :title => 'Page 1'
+ page :destination => 2, :title => 'Page 2'
end
- section 'Chapter 2', :page => 3 do
+ section 'Chapter 2', :destination => 3 do
section 'Chapter 2 Subsection' do
- page nil, :title => 'Page 3'
+ page :title => 'Page 3'
end
- page 4, :title => 'Page 4'
+ page :destination => 4, :title => 'Page 4'
end
end
start_new_page
text "Page 5. Appendix"
start_new_page
text "Page 6. More in the Appendix"
- outline.add_section do
- section 'Appendix', :page => 5 do
- page 5, :title => 'Page 5'
- page 6, :title => 'Page 6'
- end
+ outline.section 'Appendix', :destination => 5 do
+ outline.page :destination => 5, :title => 'Page 5'
+ outline.page :destination => 6, :title => 'Page 6'
end
go_to_page 4
start_new_page
text "inserted before the Appendix"
- outline.insert_section_after 'Chapter 2' do
- page page_number, :title => "Pre-Appendix"
+ outline.update do
+ insert_section_after 'Chapter 2' do
+ page :destination => page_number, :title => "Pre-Appendix"
+ end
end
go_to_page 7
start_new_page
text "One last page"
outline.insert_section_after 'Page 6' do
- page page_number, :title => "Inserted after 6"
- end
+ outline.page :destination => page_number, :title => "Inserted after 6"
+ end
+ outline.add_subsection_to 'Chapter 1', :first do
+ outline.section 'Inserted subsection', :destination => 1 do
+ outline.page :destination => 1, :title => "Page 1 again"
+ end
+ end
+ start_new_page
+ text "Really this is the last page."
+ outline.update do
+ page :destination => page_number, :title => "Last Page"
+ end
+ start_new_page
+ text "OK, I lied; this is the very last page."
+ outline.page :destination => page_number, :title => "Very Last Page"
end
View
25 lib/prawn/core/object_store.rb
@@ -21,7 +21,7 @@ class ObjectStore #:nodoc:
def initialize(opts = {})
@objects = {}
@identifiers = []
-
+
load_file(opts[:template]) if opts[:template]
@info ||= ref(opts[:info] || {}).identifier
@@ -30,10 +30,10 @@ def initialize(opts = {})
root.data[:Pages] = ref(:Type => :Pages, :Count => 0, :Kids => [])
end
end
-
+
def ref(data, &block)
push(size + 1, data, &block)
- end
+ end
def info
@objects[@info]
@@ -209,11 +209,28 @@ def load_object_graph(hash, object)
# being wrapped in a LiteralString
object
when String
- Prawn::Core::LiteralString.new(object)
+ is_utf8?(object) ? object : Prawn::Core::ByteString.new(object)
else
object
end
end
+
+ ruby_18 do
+ def is_utf8?(str)
+ begin
+ str.unpack("U*")
+ true
+ rescue
+ false
+ end
+ end
+ end
+ ruby_19 do
+ def is_utf8?(str)
+ str.force_encoding("utf-8")
+ str.valid_encoding?
+ end
+ end
end
end
end
View
4 lib/prawn/core/pdf_object.rb
@@ -61,11 +61,11 @@ def PdfObject(obj, in_content_stream = false)
when Array
"[" << obj.map { |e| PdfObject(e, in_content_stream) }.join(' ') << "]"
when Prawn::Core::LiteralString
- obj = obj.gsub(/[\\\n\(\)]/) { |m| "\\#{m}" }
+ obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/n) { |m| "\\#{m}" }
"(#{obj})"
when Time
obj = obj.strftime("D:%Y%m%d%H%M%S%z").chop.chop + "'00'"
- obj = obj.gsub(/[\\\n\(\)]/) { |m| "\\#{m}" }
+ obj = obj.gsub(/[\\\n\r\t\b\f\(\)]/n) { |m| "\\#{m}" }
"(#{obj})"
when Prawn::Core::ByteString
"<" << obj.unpack("H*").first << ">"
View
2  lib/prawn/core/text/formatted/wrap.rb
@@ -5,7 +5,7 @@ module Prawn
module Core
module Text
module Formatted #:nodoc:
- module Wrap
+ module Wrap #:nodoc:
def initialize(array, options)
super(array, options)
View
4 lib/prawn/core/text/line_wrap.rb
@@ -154,7 +154,9 @@ def remove_last_output_word
def finalize_line
@consumed_char_count = @output.length + @discarded_char_count
- @output = @output[0..-2].gsub(soft_hyphen, "") + @output[-1..-1]
+ unless @output.empty?
+ @output = @output[0..-2].gsub(soft_hyphen, "") + @output[-1..-1]
+ end
strip_trailing_whitespace
end
View
8 lib/prawn/core/text/wrap.rb
@@ -67,10 +67,12 @@ def wrap(text) #:nodoc:
private
def word_spacing_for_this_line
- if @align != :justify || @line_wrap.width.to_f / available_width.to_f < 0.75
- 0
- else
+ if @align == :justify &&
+ @line_wrap.space_count > 0 &&
+ @line_wrap.width.to_f / available_width.to_f >= 0.75
(available_width - @line_wrap.width) / @line_wrap.space_count
+ else
+ 0
end
end
View
4 lib/prawn/document.rb
@@ -481,13 +481,13 @@ def @bounding_box.move_past_bottom
success = transaction { yield }
+ @bounding_box = old_bounding_box
+
unless success
raise Prawn::Errors::CannotGroup if second_attempt
old_bounding_box.move_past_bottom
group(second_attempt=true) { yield }
end
-
- @bounding_box = old_bounding_box
end
# Specify a template for page numbering. This should be called
View
2  lib/prawn/graphics/transparency.rb
@@ -59,7 +59,7 @@ def transparent(opacity, stroke_opacity=opacity, &block)
save_graphics_state
add_content "/#{opacity_dictionary_name(opacity, stroke_opacity)} gs"
- yield if block_given?
+ yield
restore_graphics_state
end
View
15 lib/prawn/images.rb
@@ -146,9 +146,22 @@ def image_position(w,h,options)
when Numeric
bounds.absolute_top - options[:vposition]
else
- self.y
+ determine_y_with_page_flow(h)
end
return [x,y]
+ end
+
+ def determine_y_with_page_flow(h)
+ if overruns_page?(h)
+ start_new_page
+ bounds.absolute_top
+ else
+ self.y
+ end
+ end
+
+ def overruns_page?(h)
+ (self.y - h) < bounds.absolute_bottom
end
def build_jpg_object(data, jpg)
View
238 lib/prawn/outline.rb
@@ -10,23 +10,11 @@ module Prawn
class Document
- # See Outline#define below for documentation
- def define_outline(&block)
- outline.define(&block)
- end
-
- # The Outline dictionary (12.3.3) for this document. It is
- # lazily initialized, so that documents that do not have an outline
- # do not incur the additional overhead.
- def outline_root(outline_root)
- state.store.root.data[:Outlines] ||= ref!(outline_root)
- end
-
# Lazily instantiates an Outline object for document. This is used as point of entry
# to methods to build the outline tree.
def outline
@outline ||= Outline.new(self)
- end
+ end
end
@@ -49,132 +37,180 @@ class Outline
attr_accessor :parent
attr_accessor :prev
attr_accessor :document
- attr_accessor :outline_root
attr_accessor :items
def initialize(document)
@document = document
- @outline_root = document.outline_root(OutlineRoot.new)
- @parent = outline_root
+ @parent = root
@prev = nil
@items = {}
- end
+ end
- # Defines an outline for the document.
+ # Defines/Updates an outline for the document.
# The outline is an optional nested index that appears on the side of a PDF
# document usually with direct links to pages. The outline DSL is defined by nested
- # blocks involving two methods: section and page.
- #
- # section(title, options{}, &block)
- # title: the outline text that appears for the section.
- # options: page - optional integer defining the page number for a destination link.
- # - currently only :FIT destination supported with link to top of page.
- # closed - whether the section should show its nested outline elements.
- # - defaults to false.
- # page(page, options{})
- # page: integer defining the page number for the destination link.
- # currently only :FIT destination supported with link to top of page.
- # set to nil if destination link is not desired.
- # options: title - the outline text that appears for the section.
- # closed - whether the section should show its nested outline elements.
- # - defaults to false.
+ # blocks involving two methods: section and page; see the documentation on those methods
+ # for their arguments and options. Note that one can also use outline#update
+ # to add more sections to the end of the outline tree using the same syntax and scope.
#
# The syntax is best illustrated with an example:
#
- # Prawn::Document.generate(outlined document) do
+ # Prawn::Document.generate(outlined_document.pdf) do
# text "Page 1. This is the first Chapter. "
# start_new_page
# text "Page 2. More in the first Chapter. "
# start_new_page
- # define_outline do
- # section 'Chapter 1', :page => 1, :closed => true do
- # page 1, :title => 'Page 1'
- # page 2, :title => 'Page 2'
+ # outline.define do
+ # section 'Chapter 1', :destination => 1, :closed => true do
+ # page :destination => 1, :title => 'Page 1'
+ # page :destination => 2, :title => 'Page 2'
+ # end
+ # end
+ # start_new_page do
+ # outline.update do
+ # section 'Chapter 2', :destination => 2, do
+ # page :destination => 3, :title => 'Page 3'
# end
# end
- # end
- #
- # It should be noted that not defining a title for a page element will raise
- # a RequiredOption error
+ # end
#
def define(&block)
- if block
- block.arity < 1 ? instance_eval(&block) : block[self]
- end
+ instance_eval(&block) if block
end
-
- # Adds an outine section to the outline tree (see define_outline).
- # Although you will probably choose to exclusively use define_outline so
+
+ alias :update :define
+
+ # Inserts an outline section to the outline tree (see outline#define).
+ # Although you will probably choose to exclusively use outline#define so
# that your outline tree is contained and easy to manage, this method
- # gives you the option to add sections to the outline tree at any point
- # during document generation. Note that the section will be added at the
- # top level at the end of the outline. For more a more flexible API try
- # using outline.insert_section_after.
- #
- # block uses the same DSL syntax as define_outline, for example:
+ # gives you the option to insert sections to the outline tree at any point
+ # during document generation. This method allows you to add a child subsection
+ # to any other item at any level in the outline tree.
+ # Currently the only way to locate the place of entry is with the title for the
+ # item. If your title names are not unique consider using define_outline.
+ # The method takes the following arguments:
+ # title: a string that must match an outline title to add the subsection to
+ # position: either :first or :last(the default) where the subsection will be placed relative
+ # to other child elements. If you need to position your subsection in between
+ # other elements then consider using #insert_section_after
+ # block: uses the same DSL syntax as outline#define, for example:
#
- # outline.add_section do
- # section 'Added Section', :page => 3 do
- # page 3, :title => 'Page 3'
- # end
+ # Consider using this method inside of outline.update if you want to have the outline object
+ # to be scoped as self (see #insert_section_after example).
+ #
+ # go_to_page 2
+ # start_new_page
+ # text "Inserted Page"
+ # outline.add_subsection_to :title => 'Page 2', :first do
+ # outline.page :destination => page_number, :title => "Inserted Page"
# end
- def add_section(&block)
- @parent = outline_root
- @prev = outline_root.data.last
- if block
- block.arity < 1 ? instance_eval(&block) : block[self]
- end
+ #
+ def add_subsection_to(title, position = :last, &block)
+ @parent = items[title]
+ raise Prawn::Errors::UnknownOutlineTitle,
+ "\n No outline item with title: '#{title}' exists in the outline tree" unless @parent
+ @prev = position == :first ? nil : @parent.data.last
+ nxt = position == :first ? @parent.data.first : nil
+ insert_section(nxt, &block)
end
-
- # Inserts an outline section to the outline tree (see define_outline).
- # Although you will probably choose to exclusively use define_outline so
+
+ # Inserts an outline section to the outline tree (see outline#define).
+ # Although you will probably choose to exclusively use outline#define so
# that your outline tree is contained and easy to manage, this method
# gives you the option to insert sections to the outline tree at any point
# during document generation. Unlike outline.add_section, this method allows
# you to enter a section after any other item at any level in the outline tree.
# Currently the only way to locate the place of entry is with the title for the
- # item. If your titles names are not unique consider using define_outline.
- #
- # block uses the same DSL syntax as define_outline, for example:
+ # item. If your title names are not unique consider using define_outline.
+ # The method takes the following arguments:
+ # title: the title of other section or page to insert new section after
+ # block: uses the same DSL syntax as outline#define, for example:
#
# go_to_page 2
# start_new_page
# text "Inserted Page"
- # outline.insert_section_after :title => 'Page 2' do
- # page page_number, :title => "Inserted Page"
+ # update_outline do
+ # insert_section_after :title => 'Page 2' do
+ # page :destination => page_number, :title => "Inserted Page"
+ # end
# end
#
def insert_section_after(title, &block)
@prev = items[title]
- if @prev
- @parent = @prev.data.parent
- nxt = @prev.data.next
- if block
- block.arity < 1 ? instance_eval(&block) : block[self]
- end
- adjust_relations(nxt)
- else
- raise Prawn::Errors::UnknownOutlineTitle,
- "\n No outline item with title: '#{title}' exists in the outline tree"
- end
+ raise Prawn::Errors::UnknownOutlineTitle,
+ "\n No outline item with title: '#{title}' exists in the outline tree" unless @prev
+ @parent = @prev.data.parent
+ nxt = @prev.data.next
+ insert_section(nxt, &block)
end
-
- private
-
+
+ # See outline#define above for documentation on how this is used in that context
+ #
+ # Adds an outine section to the outline tree.
+ # Although you will probably choose to exclusively use outline#define so
+ # that your outline tree is contained and easy to manage, this method
+ # gives you the option to add sections to the outline tree at any point
+ # during document generation. When not being called from within another #section block
+ # the section will be added at the top level after the other root elements of the outline.
+ # For more flexible placement try using outline#insert_section_after and/or
+ # outline#add_subsection_to
+ # Takes the following arguments:
+ # title: the outline text that appears for the section.
+ # options: destination - optional integer defining the page number for a destination link.
+ # - currently only :FIT destination supported with link to top of page.
+ # closed - whether the section should show its nested outline elements.
+ # - defaults to false.
+ # block: more nested subsections and/or page blocks
+ #
+ # example usage:
+ #
+ # outline.section 'Added Section', :destination => 3 do
+ # outline.page :destionation => 3, :title => 'Page 3'
+ # end
def section(title, options = {}, &block)
add_outline_item(title, options, &block)
end
- def page(page = nil, options = {})
+ # See Outline#define above for more documentation on how it is used in that context
+ #
+ # Adds a page to the outline.
+ # Although you will probably choose to exclusively use outline#define so
+ # that your outline tree is contained and easy to manage, this method also
+ # gives you the option to add pages to the root of outline tree at any point
+ # during document generation. Note that the page will be added at the
+ # top level after the other root outline elements. For more flexible placement try
+ # using outline#insert_section_after and/or outline#add_subsection_to.
+ #
+ # Takes the following arguments:
+ # options:
+ # title - REQUIRED. The outline text that appears for the page.
+ # destination - integer defining the page number for the destination link.
+ # currently only :FIT destination supported with link to top of page.
+ # closed - whether the section should show its nested outline elements.
+ # - defaults to false.
+ # example usage:
+ #
+ # outline.page :title => "Very Last Page"
+ # Note: this method is almost identical to section except that it does not accept a block
+ # thereby defining the outline item as a leaf on the outline tree structure.
+ def page(options = {})
if options[:title]
title = options[:title]
- options[:page] = page
else
raise Prawn::Errors::RequiredOption,
"\nTitle is a required option for page"
end
add_outline_item(title, options)
end
+
+ private
+
+ # The Outline dictionary (12.3.3) for this document. It is
+ # lazily initialized, so that documents that do not have an outline
+ # do not incur the additional overhead.
+ def root
+ document.state.store.root.data[:Outlines] ||= document.ref!(OutlineRoot.new)
+ end
def add_outline_item(title, options, &block)
outline_item = create_outline_item(title, options)
@@ -188,8 +224,8 @@ def add_outline_item(title, options, &block)
def create_outline_item(title, options)
outline_item = OutlineItem.new(title, parent, options)
- if options[:page]
- page_index = options[:page] - 1
+ if options[:destination]
+ page_index = options[:destination] - 1
outline_item.dest = [document.state.pages[page_index].dictionary, :Fit]
end
@@ -207,7 +243,7 @@ def increase_count
counting_parent = parent
while counting_parent
counting_parent.data.count += 1
- if counting_parent == outline_root
+ if counting_parent == root
counting_parent = nil
else
counting_parent = counting_parent.data.parent
@@ -225,16 +261,28 @@ def reset_parent(outline_item)
self.prev = outline_item
self.parent = outline_item.data.parent
end
+ end
+
+ def insert_section(nxt, &block)
+ last = @parent.data.last
+ if block
+ block.call
+ end
+ adjust_relations(nxt, last)
+ reset_root_positioning
end
- def adjust_relations(nxt)
+ def adjust_relations(nxt, last)
if nxt
nxt.data.prev = @prev
@prev.data.next = nxt
- @parent.data.last = nxt
- else
- @parent.data.last = @prev
+ @parent.data.last = last
end
+ end
+
+ def reset_root_positioning
+ @parent = root
+ @prev = root.data.last
end
end
View
2  lib/prawn/table.rb
@@ -6,7 +6,7 @@
#
# This is free software. Please see the LICENSE and COPYING files for details.
-require 'prawn/table/accessors'
+require 'prawn/table/cells'
require 'prawn/table/cell'
require 'prawn/table/cell/in_table'
require 'prawn/table/cell/text'
View
12 lib/prawn/table/cell.rb
@@ -20,6 +20,16 @@ def cell(options={})
cell.draw
cell
end
+
+ # Set up, but do not draw, a cell. Useful for creating cells with
+ # formatting options to be inserted into a Table. Call +draw+ on the
+ # resulting Cell to ink it.
+ #
+ # See the documentation on Prawn::Cell for details on the arguments.
+ #
+ def make_cell(content, options={})
+ Prawn::Table::Cell.make(self, content, options)
+ end
end
@@ -200,12 +210,12 @@ def natural_content_height
#
def draw(pt=[x, y])
draw_background(pt)
- draw_borders(pt)
@pdf.bounding_box([pt[0] + padding_left, pt[1] - padding_top],
:width => content_width + FPTolerance,
:height => content_height + FPTolerance) do
draw_content
end
+ draw_borders(pt)
end
# x-position of the cell within the parent bounds.
View
95 lib/prawn/table/accessors.rb → lib/prawn/table/cells.rb
@@ -1,32 +1,31 @@
# encoding: utf-8
-# accessors.rb: Methods for accessing rows, columns, and cells of a
-# Prawn::Table.
+# cells.rb: Methods for accessing rows, columns, and cells of a Prawn::Table.
#
# Copyright December 2009, Brad Ediger. All Rights Reserved.
#
# This is free software. Please see the LICENSE and COPYING files for details.
+
module Prawn
-
class Table
- # Returns a CellProxy that can be used to select and style cells. See the
- # CellProxy documentation for things you can do with cells.
+ # Returns a Cells object that can be used to select and style cells. See
+ # the Cells documentation for things you can do with cells.
#
def cells
- @cell_proxy ||= CellProxy.new(@cells)
+ @cell_proxy ||= Cells.new(@cells)
end
- # Selects the given rows (0-based) for styling. Returns a CellProxy -- see
- # the documentation on CellProxy for things you can do with cells.
+ # Selects the given rows (0-based) for styling. Returns a Cells object --
+ # see the documentation on Cells for things you can do with cells.
#
def rows(row_spec)
cells.rows(row_spec)
end
alias_method :row, :rows
- # Selects the given columns (0-based) for styling. Returns a CellProxy --
- # see the documentation on CellProxy for things you can do with cells.
+ # Selects the given columns (0-based) for styling. Returns a Cells object
+ # -- see the documentation on Cells for things you can do with cells.
#
def columns(col_spec)
cells.columns(col_spec)
@@ -44,18 +43,7 @@ def columns(col_spec)
#
# table.rows(1..3).columns(2..4).background_color = 'ff0000'
#
- class CellProxy
- def initialize(cells) #:nodoc:
- @cells = cells
- end
-
- # Iterates over cells in turn.
- #
- def each(&b)
- @cells.each(&b)
- end
-
- include Enumerable
+ class Cells < Array
# Limits selection to the given row or rows. +row_spec+ can be anything
# that responds to the === operator selecting a set of 0-based row
@@ -65,10 +53,11 @@ def each(&b)
# table.rows(3..4) # selects rows four and five
#
def rows(row_spec)
- CellProxy.new(@cells.select { |c| row_spec === c.row })
+ index_cells unless @indexed
+ Cells.new(@rows[row_spec] ||= select{ |c| row_spec === c.row })
end
alias_method :row, :rows
-
+
# Limits selection to the given column or columns. +col_spec+ can be
# anything that responds to the === operator selecting a set of 0-based
# column numbers; most commonly a number or a range.
@@ -76,27 +65,28 @@ def rows(row_spec)
# table.column(0) # selects first column
# table.columns(3..4) # selects columns four and five
#
- def columns(col_spec)
- CellProxy.new(@cells.select { |c| col_spec === c.column })
+ def columns(row_spec)
+ index_cells unless @indexed
+ Cells.new(@columns[row_spec] ||= select{ |c| row_spec === c.column })
end
alias_method :column, :columns
- # Selects cells based on a block.
+ # Allows you to filter the given cells by arbitrary properties.
#
- # table.column(4).select { |cell| cell.content =~ /Yes/ }.
- # background_color = 'ff0000'
+ # table.column(4).filter { |cell| cell.content =~ /Yes/ }.
+ # background_color = '00ff00'
#
- def select(&b)
- CellProxy.new(@cells.select(&b))
+ def filter(&block)
+ Cells.new(select(&block))
end
- # Retrieves a cell based on its 0-based row and column. Returns a Cell,
- # not a CellProxy.
+ # Retrieves a cell based on its 0-based row and column. Returns an
+ # individual Cell, not a Cells collection.
#
# table.cells[0, 0].content # => "First cell content"
#
def [](row, col)
- @cells.find { |c| c.row == row && c.column == col }
+ find { |c| c.row == row && c.column == col }
end
# Supports setting multiple properties at once.
@@ -114,7 +104,7 @@ def [](row, col)
# table.cells.style { |cell| cell.border_width += 12 }
#
def style(options={}, &block)
- @cells.each do |cell|
+ each do |cell|
options.each { |k, v| cell.send("#{k}=", v) }
block.call(cell) if block
end
@@ -124,7 +114,7 @@ def style(options={}, &block)
#
def width
column_widths = {}
- @cells.each do |cell|
+ each do |cell|
column_widths[cell.column] =
[column_widths[cell.column], cell.width].compact.max
end
@@ -135,7 +125,7 @@ def width
#
def min_width
column_min_widths = {}
- @cells.each do |cell|
+ each do |cell|
column_min_widths[cell.column] =
[column_min_widths[cell.column], cell.min_width].compact.max
end
@@ -146,7 +136,7 @@ def min_width
#
def max_width
column_max_widths = {}
- @cells.each do |cell|
+ each do |cell|
column_max_widths[cell.column] =
[column_max_widths[cell.column], cell.max_width].compact.min
end
@@ -157,7 +147,7 @@ def max_width
#
def height
row_heights = {}
- @cells.each do |cell|
+ each do |cell|
row_heights[cell.row] =
[row_heights[cell.row], cell.height].compact.max
end
@@ -169,12 +159,31 @@ def height
# table.cells.row(3..6).background_color = 'cc0000'
#
def method_missing(id, *args, &block)
- @cells.each { |c| c.send(id, *args, &block) }
+ each { |c| c.send(id, *args, &block) }
end
- end
- end
+ protected
+
+ # Defers indexing until rows() or columns() is actually called on the
+ # Cells object. Without this, we would needlessly index the leaf nodes of
+ # the object graph, the ones that are only there to be iterated over.
+ #
+ # Make sure to call this before using @rows or @columns.
+ #
+ def index_cells
+ @rows = {}
+ @columns = {}
-end
+ each do |cell|
+ @rows[cell.row] ||= []
+ @rows[cell.row] << cell
+ @columns[cell.column] ||= []
+ @columns[cell.column] << cell
+ end
+ @indexed = true
+ end
+ end
+ end
+end
View
17 lib/prawn/text/box.rb
@@ -280,25 +280,18 @@ def draw_line(line_to_print, line_width=0, word_spacing=0, include_ellipses=fals
when :right
x = @at[0] + @width - line_width
end
-
+
y = @at[1] + @baseline_y
-
+
if @inked
- if @align == :justify
- @document.word_spacing(word_spacing) {
- @document.character_spacing(@character_spacing) {
- @document.draw_text!(line_to_print, :at => [x, y],
- :kerning => @kerning)
- }
- }
- else
+ @document.word_spacing(word_spacing) {
@document.character_spacing(@character_spacing) {
@document.draw_text!(line_to_print, :at => [x, y],
:kerning => @kerning)
}
- end
+ }
end
-
+
line_to_print
end
View
10 lib/prawn/text/formatted/box.rb
@@ -136,16 +136,10 @@ def draw_fragment(fragment, accumulated_width=0, line_width=0, word_spacing=0) #
draw_fragment_underlays(fragment)
if @inked
- if @align == :justify
- @document.word_spacing(word_spacing) {
- @document.draw_text!(fragment.text, :at => [x, y],
- :kerning => @kerning)
- }
- else
+ @document.word_spacing(word_spacing) {
@document.draw_text!(fragment.text, :at => [x, y],
:kerning => @kerning)
- end
-
+ }
draw_fragment_overlays(fragment)
end
end
View
11 spec/cell_spec.rb
@@ -53,6 +53,17 @@ def close?(actual, expected, epsilon=0.01)
:style => :bold)
end
end
+
+ describe "Prawn::Document#make_cell" do
+ it "should not draw the cell" do
+ Prawn::Table::Cell::Text.any_instance.expects(:draw).never
+ @pdf.make_cell("text")
+ end
+
+ it "should return a Cell" do
+ @pdf.make_cell("text", :size => 7).should.be.a.kind_of Prawn::Table::Cell
+ end
+ end
describe "cell width" do
include CellHelpers
View
21 spec/images_spec.rb
@@ -63,6 +63,27 @@
output.should =~ /\/BitsPerComponent 8/
end
+ it "should flow an image to a new page if it will not fit on a page" do
+ @pdf.image @filename, :fit => [600, 600]
+ @pdf.image @filename, :fit => [600, 600]
+ output = StringIO.new(@pdf.render, 'r+')
+ hash = PDF::Hash.new(output)
+ pages = hash.values.find {|obj| obj.is_a?(Hash) && obj[:Type] == :Pages}[:Kids]
+ pages.size.should == 2
+ hash[pages[0]][:Resources][:XObject].keys.should == [:I1]
+ hash[pages[1]][:Resources][:XObject].keys.should == [:I2]
+ end
+
+ it "should not flow an image to a new page if it will fit on one page" do
+ @pdf.image @filename, :fit => [400, 400]
+ @pdf.image @filename, :fit => [400, 400]
+ output = StringIO.new(@pdf.render, 'r+')
+ hash = PDF::Hash.new(output)
+ pages = hash.values.find {|obj| obj.is_a?(Hash) && obj[:Type] == :Pages}[:Kids]
+ pages.size.should == 1
+ hash[pages[0]][:Resources][:XObject].keys.should == [:I1, :I2]
+ end
+
describe ":fit option" do
it "should fit inside the defined constraints" do
info = @pdf.image @filename, :fit => [100,400]
View
189 spec/outline_spec.rb
@@ -8,10 +8,10 @@
start_new_page
text "Page 2. More in the first Chapter. "
start_new_page
- define_outline do
- section 'Chapter 1', :page => 1, :closed => true do
- page 1, :title => 'Page 1'
- page 2, :title => 'Page 2'
+ outline.define do
+ section 'Chapter 1', :destination => 1, :closed => true do
+ page :destination => 1, :title => 'Page 1'
+ page :destination => 2, :title => 'Page 2'
end
end
end
@@ -87,13 +87,13 @@
end
- describe "#outline.add_section" do
+ describe "addding a section later with outline#section" do
before(:each) do
@pdf.start_new_page
@pdf.text "Page 3. An added section "
- @pdf.outline.add_section do
- section 'Added Section', :page => 3 do
- page 3, :title => 'Page 3'
+ @pdf.outline.update do
+ section 'Added Section', :destination => 3 do
+ page :destination => 3, :title => 'Page 3'
end
end
render_and_find_objects
@@ -120,7 +120,116 @@
@outline_root[:Count].should == 5
end
- end
+ end
+
+ describe "#outline.add_subsection_to" do
+ context "positioned last" do
+
+ before(:each) do
+ @pdf.start_new_page
+ @pdf.text "Page 3. An added subsection "
+ @pdf.outline.update do
+ add_subsection_to 'Chapter 1' do
+ section 'Added SubSection', :destination => 3 do
+ page :destination => 3, :title => 'Added Page 3'
+ end
+ end
+ end
+ render_and_find_objects
+ end
+
+ it "should add new outline items to document" do
+ [@subsection, @added_page_3].each { |item| assert_not_nil item}
+ end
+
+ it "should reset the last item for parent item dictionary" do
+ referenced_object(@section_1[:First]).should == @page_1
+ referenced_object(@section_1[:Last]).should == @subsection
+ end
+
+ it "should set the prev relation for the new subsection to its parent's old last item" do
+ referenced_object(@subsection[:Prev]).should == @page_2
+ end
+
+
+ it "the subsection should become the next relation for its parent's old last item" do
+ referenced_object(@page_2[:Next]).should == @subsection
+ end
+
+ it "should set the first relation for the new subsection" do
+ referenced_object(@subsection[:First]).should == @added_page_3
+ end
+
+ it "should set the correct last relation of the added to section" do
+ referenced_object(@subsection[:Last]).should == @added_page_3
+ end
+
+ it "should increase the count of root outline dictionary" do
+ @outline_root[:Count].should == 5
+ end
+
+ end
+
+ context "positioned first" do
+
+ before(:each) do
+ @pdf.start_new_page
+ @pdf.text "Page 3. An added subsection "
+ @pdf.outline.update do
+ add_subsection_to 'Chapter 1', :first do
+ section 'Added SubSection', :destination => 3 do
+ page :destination => 3, :title => 'Added Page 3'
+ end
+ end
+ end
+ render_and_find_objects
+ end
+
+ it "should add new outline items to document" do
+ [@subsection, @added_page_3].each { |item| assert_not_nil item}
+ end
+
+ it "should reset the first item for parent item dictionary" do
+ referenced_object(@section_1[:First]).should == @subsection
+ referenced_object(@section_1[:Last]).should == @page_2
+ end
+
+ it "should set the next relation for the new subsection to its parent's old first item" do
+ referenced_object(@subsection[:Next]).should == @page_1
+ end
+
+ it "the subsection should become the prev relation for its parent's old first item" do
+ referenced_object(@page_1[:Prev]).should == @subsection
+ end
+
+ it "should set the first relation for the new subsection" do
+ referenced_object(@subsection[:First]).should == @added_page_3
+ end
+
+ it "should set the correct last relation of the added to section" do
+ referenced_object(@subsection[:Last]).should == @added_page_3
+ end
+
+ it "should increase the count of root outline dictionary" do
+ @outline_root[:Count].should == 5
+ end
+
+ end
+
+ it "should require an existing title" do
+ assert_raise Prawn::Errors::UnknownOutlineTitle do
+ @pdf.go_to_page 1
+ @pdf.start_new_page
+ @pdf.text "Inserted Page"
+ @pdf.outline.update do
+ add_subsection_to 'Wrong page' do
+ page page_number, :title => "Inserted Page"
+ end
+ end
+ render_and_find_objects
+ end
+ end
+ end
describe "#outline.insert_section_after" do
describe "inserting in the middle of another section" do
@@ -128,8 +237,10 @@
@pdf.go_to_page 1
@pdf.start_new_page
@pdf.text "Inserted Page"
- @pdf.outline.insert_section_after 'Page 1' do
- page page_number, :title => "Inserted Page"
+ @pdf.outline.update do
+ insert_section_after 'Page 1' do
+ page :destination => page_number, :title => "Inserted Page"
+ end
end
render_and_find_objects
end
@@ -137,40 +248,60 @@
it "should insert new outline items to document" do
assert_not_nil @inserted_page
end
-
+
it "should adjust the count of all ancestors" do
@outline_root[:Count].should == 4
@section_1[:Count].should.abs == 3
end
-
+
describe "#adjust_relations" do
-
+
it "should reset the sibling relations of adjoining items to inserted item" do
referenced_object(@page_1[:Next]).should == @inserted_page
referenced_object(@page_2[:Prev]).should == @inserted_page
end
-
+
it "should set the sibling relation of added item to adjoining items" do
referenced_object(@inserted_page[:Next]).should == @page_2
referenced_object(@inserted_page[:Prev]).should == @page_1
end
-
+
it "should not affect the first and last relations of parent item" do
referenced_object(@section_1[:First]).should == @page_1
referenced_object(@section_1[:Last]).should == @page_2
end
-
+
+ end
+
+
+ context "when adding another section afterwards" do
+ it "should have reset the root position so that a new section is added at the end of root sections" do
+ @pdf.start_new_page
+ @pdf.text "Another Inserted Page"
+ @pdf.outline.update do
+ section 'Added Section' do
+ page :destination => page_number, :title => "Inserted Page"
+ end
+ end
+ render_and_find_objects
+ referenced_object(@outline_root[:Last]).should == @section_2
+ referenced_object(@section_1[:Next]).should == @section_2
+ end
end
- end
+ end
+
describe "inserting at the end of another section" do
+
before(:each) do
@pdf.go_to_page 2
@pdf.start_new_page
@pdf.text "Inserted Page"
- @pdf.outline.insert_section_after 'Page 2' do
- page page_number, :title => "Inserted Page"
+ @pdf.outline.update do
+ insert_section_after 'Page 2' do
+ page :destination => page_number, :title => "Inserted Page"
+ end
end
render_and_find_objects
end
@@ -198,8 +329,10 @@
@pdf.go_to_page 1
@pdf.start_new_page
@pdf.text "Inserted Page"
- @pdf.outline.insert_section_after 'Wrong page' do
- page page_number, :title => "Inserted Page"
+ @pdf.outline.update do
+ insert_section_after 'Wrong page' do
+ page :destination => page_number, :title => "Inserted Page"
+ end
end
render_and_find_objects
end
@@ -212,8 +345,8 @@
assert_raise Prawn::Errors::RequiredOption do
@pdf = Prawn::Document.new() do
text "Page 1. This is the first Chapter. "
- define_outline do
- page 1, :title => nil
+ outline.define do
+ page :destination => 1, :title => nil
end
end
end
@@ -224,8 +357,8 @@
context "foreign character encoding" do
before(:each) do
pdf = Prawn::Document.new() do
- define_outline do
- section 'La pomme croquée', :page => 1, :closed => true
+ outline.define do
+ section 'La pomme croquée', :destination => 1, :closed => true
end
end
@hash = PDF::Hash.new(StringIO.new(pdf.render, 'r+'))
@@ -247,7 +380,9 @@ def render_and_find_objects
@page_2 = find_by_title('Page 2')
@section_2 = find_by_title('Added Section')
@page_3 = find_by_title('Page 3')
- @inserted_page = find_by_title('Inserted Page')
+ @inserted_page = find_by_title('Inserted Page')
+ @subsection = find_by_title('Added SubSection')
+ @added_page_3 = find_by_title('Added Page 3')
end
# Outline titles are stored as UTF-16. This method accepts a UTF-8 outline title
View
38 spec/pdf_object_spec.rb
@@ -56,7 +56,43 @@
s = 'I can \\)( has string'
PDF::Inspector.parse(Prawn::Core::PdfObject(s, true)).should == s
end
-
+
+ it "should escape various strings correctly when converting a LiteralString" do
+ ls = Prawn::Core::LiteralString.new("abc")
+ Prawn::Core::PdfObject(ls).should == "(abc)"
+
+ ls = Prawn::Core::LiteralString.new("abc\x0Ade") # should escape \n
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C\x0Ade)"
+
+ ls = Prawn::Core::LiteralString.new("abc\x0Dde") # should escape \r
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C\x0Dde)"
+
+ ls = Prawn::Core::LiteralString.new("abc\x09de") # should escape \t
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C\x09de)"
+
+ ls = Prawn::Core::LiteralString.new("abc\x08de") # should escape \b
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C\x08de)"
+
+ ls = Prawn::Core::LiteralString.new("abc\x0Cde") # should escape \f
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C\x0Cde)"
+
+ ls = Prawn::Core::LiteralString.new("abc(de") # should escape \(
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C(de)"
+
+ ls = Prawn::Core::LiteralString.new("abc)de") # should escape \)
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C)de)"
+
+ ls = Prawn::Core::LiteralString.new("abc\x5Cde") # should escape \\
+ Prawn::Core::PdfObject(ls).should == "(abc\x5C\x5Cde)"
+ Prawn::Core::PdfObject(ls).size.should == 9
+ end
+
+ it "should escape strings correctly when converting a LiteralString that is not utf-8" do
+ data = "\x43\xaf\xc9\x7f\xef\xf\xe6\xa8\xcb\x5c\xaf\xd0"
+ ls = Prawn::Core::LiteralString.new(data)
+ Prawn::Core::PdfObject(ls).should == "(\x43\xaf\xc9\x7f\xef\xf\xe6\xa8\xcb\x5c\x5c\xaf\xd0)"
+ end
+
it "should convert a Ruby symbol to PDF name" do
Prawn::Core::PdfObject(:my_symbol).should == "/my_symbol"
Prawn::Core::PdfObject(:"A;Name_With-Various***Characters?").should ==
View
16 spec/snapshot_spec.rb
@@ -64,6 +64,22 @@
pages.size.should == 1
end
+ it "should not propagate a RollbackTransaction outside its bounds" do
+ def add_lines(pdf)
+ 100.times { |i| pdf.text "Line #{i}" }
+ end
+
+ Prawn::Document.new do |pdf|
+ lambda do
+ begin
+ pdf.group { add_lines(pdf) }
+ rescue Prawn::Errors::CannotGroup
+ add_lines(pdf)
+ end
+ end.should.not.raise#(Prawn::Document::Snapshot::RollbackTransaction)
+ end
+ end
+
# Because the Pages object, when restored, points to the snapshotted pages
# by identifier, we have to restore the snapshot into the same page objects,
# or else old pages will appear in the post-rollback document.
View
4 spec/table_spec.rb
@@ -110,8 +110,8 @@
@table.row(0).column(1).map { |c| c.content }.should == ["R0C1"]
end
- it "should accept a select block, returning a cell proxy" do
- @table.cells.select { |c| c.content =~ /R0/ }.column(1).map{ |c|
+ it "should accept a filter block, returning a cell proxy" do
+ @table.cells.filter { |c| c.content =~ /R0/ }.column(1).map{ |c|
c.content }.should == ["R0C1"]
end
View
9 spec/text_spec.rb
@@ -43,6 +43,15 @@
describe "#text" do
before(:each) { create_pdf }
+ it "should not fail when @output is nil when Prawn::Core::Text::LineWrap#finalize_line is called" do
+ # need a document with margins for these particulars to produce the
+ # condition that was throwing the error
+ pdf = Prawn::Document.new
+ lambda {
+ pdf.text "transparency " * 150, :size => 18
+ }.should.not.raise(TypeError)
+ end
+
it "should default to use kerning information" do
@pdf.text "hello world"
text = PDF::Inspector::Text.analyze(@pdf.render)
View
36 spec/transparency_spec.rb
@@ -1,9 +1,19 @@
require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
+module TransparencyHelper
+ def make_transparent(opacity, stroke_opacity=opacity)
+ @pdf.transparent(opacity, stroke_opacity) do
+ yield if block_given?
+ end
+ end
+end
+
describe "Document with transparency" do
+ include TransparencyHelper
+
it "the PDF version should be at least 1.4" do
create_pdf
- @pdf.transparent(0.5)
+ make_transparent(0.5)
str = @pdf.render
str[0,8].should == "%PDF-1.4"
end
@@ -11,8 +21,9 @@
it "a new extended graphics state should be created for "+
"each unique transparency setting" do
create_pdf
- @pdf.transparent(0.5, 0.2)
- @pdf.transparent(0.5, 0.75)
+ make_transparent(0.5, 0.2) do
+ make_transparent(0.5, 0.75)
+ end
extgstates = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates
extgstates.length.should == 2
end
@@ -20,8 +31,9 @@
it "a new extended graphics state should not be created for "+
"each duplicate transparency setting" do
create_pdf
- @pdf.transparent(0.5, 0.75)
- @pdf.transparent(0.5, 0.75)
+ make_transparent(0.5, 0.75) do
+ make_transparent(0.5, 0.75)
+ end
extgstates = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates
extgstates.length.should == 1
end
@@ -29,7 +41,7 @@
it "setting the transparency with only one parameter sets the transparency"+
" for both the fill and the stroke" do
create_pdf
- @pdf.transparent(0.5)
+ make_transparent(0.5)
extgstate = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates[0]
extgstate[:opacity].should == 0.5
extgstate[:stroke_opacity].should == 0.5
@@ -39,7 +51,7 @@
"a :stroke should set the fill transparency to the numerical parameter "+
"and the stroke transparency to the option" do
create_pdf
- @pdf.transparent(0.5, 0.2)
+ make_transparent(0.5, 0.2)
extgstate = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates[0]
extgstate[:opacity].should == 0.5
extgstate[:stroke_opacity].should == 0.2
@@ -47,24 +59,26 @@
it "should enforce the valid range of 0.0 to 1.0" do
create_pdf
- @pdf.transparent(-0.5, -0.2)
+ make_transparent(-0.5, -0.2)
extgstate = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates[0]
extgstate[:opacity].should == 0.0
extgstate[:stroke_opacity].should == 0.0
create_pdf
- @pdf.transparent(2.0, 3.0)
+ make_transparent(2.0, 3.0)
extgstate = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates[0]
extgstate[:opacity].should == 1.0
extgstate[:stroke_opacity].should == 1.0
end
describe "with more than one page" do
+ include TransparencyHelper
+
it "the extended graphic state resource should be added to both pages" do
create_pdf
- @pdf.transparent(0.5, 0.2)
+ make_transparent(0.5, 0.2)
@pdf.start_new_page
- @pdf.transparent(0.5, 0.2)
+ make_transparent(0.5, 0.2)
extgstates = PDF::Inspector::ExtGState.analyze(@pdf.render).extgstates
extgstate = extgstates[0]
extgstates.length.should == 2
Please sign in to comment.
Something went wrong with that request. Please try again.