prawn-core 0.5.1 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/HACKING +46 -0
  2. data/README +9 -3
  3. data/Rakefile +7 -6
  4. data/examples/bounding_box/stretched_nesting.rb +67 -0
  5. data/examples/general/margin.rb +36 -0
  6. data/examples/general/multi_page_layout.rb +3 -1
  7. data/examples/general/page_numbering.rb +15 -0
  8. data/examples/general/stamp.rb +45 -0
  9. data/examples/graphics/stroke_cap_and_join.rb +45 -0
  10. data/examples/graphics/stroke_dash.rb +42 -0
  11. data/examples/graphics/transparency.rb +26 -0
  12. data/examples/text/text_box_returning_excess.rb +51 -0
  13. data/lib/prawn/byte_string.rb +7 -0
  14. data/lib/prawn/core.rb +7 -8
  15. data/lib/prawn/document/annotations.rb +3 -2
  16. data/lib/prawn/document/bounding_box.rb +15 -10
  17. data/lib/prawn/document/column_box.rb +1 -3
  18. data/lib/prawn/document/destinations.rb +11 -10
  19. data/lib/prawn/document/internals.rb +62 -19
  20. data/lib/prawn/document/snapshot.rb +71 -0
  21. data/lib/prawn/document/text/box.rb +7 -0
  22. data/lib/prawn/document/text/wrapping.rb +3 -0
  23. data/lib/prawn/document/text.rb +9 -2
  24. data/lib/prawn/document.rb +141 -25
  25. data/lib/prawn/errors.rb +12 -0
  26. data/lib/prawn/font/afm.rb +1 -1
  27. data/lib/prawn/font/ttf.rb +5 -5
  28. data/lib/prawn/font.rb +8 -5
  29. data/lib/prawn/graphics/cap_style.rb +35 -0
  30. data/lib/prawn/graphics/dash.rb +69 -0
  31. data/lib/prawn/graphics/join_style.rb +35 -0
  32. data/lib/prawn/graphics/transparency.rb +56 -0
  33. data/lib/prawn/graphics.rb +9 -1
  34. data/lib/prawn/images.rb +4 -4
  35. data/lib/prawn/name_tree.rb +2 -1
  36. data/lib/prawn/object_store.rb +63 -0
  37. data/lib/prawn/pdf_object.rb +4 -0
  38. data/lib/prawn/reference.rb +18 -5
  39. data/lib/prawn/stamp.rb +87 -0
  40. data/spec/bounding_box_spec.rb +9 -0
  41. data/spec/document_spec.rb +58 -5
  42. data/spec/images_spec.rb +1 -1
  43. data/spec/name_tree_spec.rb +14 -5
  44. data/spec/object_store_spec.rb +42 -0
  45. data/spec/pdf_object_spec.rb +5 -0
  46. data/spec/reference_spec.rb +40 -0
  47. data/spec/snapshot_spec.rb +115 -0
  48. data/spec/spec_helper.rb +1 -4
  49. data/spec/stamp_spec.rb +98 -0
  50. data/spec/stroke_styles_spec.rb +152 -0
  51. data/spec/text_box_spec.rb +26 -0
  52. data/spec/text_spec.rb +8 -1
  53. data/spec/transparency_spec.rb +61 -0
  54. data/vendor/pdf-inspector/lib/pdf/inspector/extgstate.rb +18 -0
  55. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +40 -1
  56. data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +12 -3
  57. data/vendor/pdf-inspector/lib/pdf/inspector.rb +2 -1
  58. metadata +26 -2
@@ -11,6 +11,7 @@ require 'prawn/name_tree'
11
11
  module Prawn
12
12
  class Document
13
13
  module Destinations
14
+
14
15
  # The maximum number of children to fit into a single node in the Dests tree.
15
16
  NAME_TREE_CHILDREN_LIMIT = 20 #:nodoc:
16
17
 
@@ -19,7 +20,7 @@ module Prawn
19
20
  # (For more on name trees, see section 3.8.4 in the PDF spec.)
20
21
  #
21
22
  def dests
22
- names.data[:Dests] ||= ref(Prawn::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT))
23
+ names.data[:Dests] ||= ref!(Prawn::NameTree::Node.new(self, NAME_TREE_CHILDREN_LIMIT))
23
24
  end
24
25
 
25
26
  # Adds a new destination to the dests name tree (see #dests). The
@@ -27,63 +28,63 @@ module Prawn
27
28
  # it is not already one.
28
29
  #
29
30
  def add_dest(name, reference)
30
- reference = ref(reference) unless reference.is_a?(Prawn::Reference)
31
+ reference = ref!(reference) unless reference.is_a?(Prawn::Reference)
31
32
  dests.data.add(name, reference)
32
33
  end
33
34
 
34
35
  # Return a Dest specification for a specific location (and optional zoom
35
36
  # level).
36
37
  #
37
- def dest_xyz(left, top, zoom=nil, page=@current_page)
38
+ def dest_xyz(left, top, zoom=nil, page=current_page)
38
39
  [page, :XYZ, left, top, zoom]
39
40
  end
40
41
 
41
42
  # Return a Dest specification that will fit the given page into the
42
43
  # viewport.
43
44
  #
44
- def dest_fit(page=@current_page)
45
+ def dest_fit(page=current_page)
45
46
  [page, :Fit]
46
47
  end
47
48
 
48
49
  # Return a Dest specification that will fit the given page horizontally
49
50
  # into the viewport, aligned vertically at the given top coordinate.
50
51
  #
51
- def dest_fit_horizontally(top, page=@current_page)
52
+ def dest_fit_horizontally(top, page=current_page)
52
53
  [page, :FitH, top]
53
54
  end
54
55
 
55
56
  # Return a Dest specification that will fit the given page vertically
56
57
  # into the viewport, aligned horizontally at the given left coordinate.
57
58
  #
58
- def dest_fit_vertically(left, page=@current_page)
59
+ def dest_fit_vertically(left, page=current_page)
59
60
  [page, :FitV, left]
60
61
  end
61
62
 
62
63
  # Return a Dest specification that will fit the given rectangle into the
63
64
  # viewport, for the given page.
64
65
  #
65
- def dest_fit_rect(left, bottom, right, top, page=@current_page)
66
+ def dest_fit_rect(left, bottom, right, top, page=current_page)
66
67
  [page, :FitR, left, bottom, right, top]
67
68
  end
68
69
 
69
70
  # Return a Dest specfication that will fit the given page's bounding box
70
71
  # into the viewport.
71
72
  #
72
- def dest_fit_bounds(page=@current_page)
73
+ def dest_fit_bounds(page=current_page)
73
74
  [page, :FitB]
74
75
  end
75
76
 
76
77
  # Same as #dest_fit_horizontally, but works on the page's bounding box
77
78
  # instead of the entire page.
78
79
  #
79
- def dest_fit_bounds_horizontally(top, page=@current_page)
80
+ def dest_fit_bounds_horizontally(top, page=current_page)
80
81
  [page, :FitBH, top]
81
82
  end
82
83
 
83
84
  # Same as #dest_fit_vertically, but works on the page's bounding box
84
85
  # instead of the entire page.
85
86
  #
86
- def dest_fit_bounds_vertically(left, page=@current_page)
87
+ def dest_fit_bounds_vertically(left, page=current_page)
87
88
  [page, :FitBV, left]
88
89
  end
89
90
  end
@@ -17,7 +17,9 @@ module Prawn
17
17
  module Internals
18
18
 
19
19
  # Creates a new Prawn::Reference and adds it to the Document's object
20
- # list. The +data+ argument is anything that Prawn::PdfObject() can convert.
20
+ # list. The +data+ argument is anything that Prawn::PdfObject() can convert.
21
+ #
22
+ # Returns the identifier which points to the reference in the ObjectStore
21
23
  #
22
24
  # If a block is given, it will be invoked just before the object is written
23
25
  # out to the PDF document stream. This allows you to do deferred processing
@@ -25,8 +27,40 @@ module Prawn
25
27
  # about until the last page of the document is finished).
26
28
  #
27
29
  def ref(data, &block)
28
- @objects.push(Prawn::Reference.new(@objects.size + 1, data, &block)).last
30
+ ref!(data, &block).identifier
29
31
  end
32
+
33
+ # Like ref, but returns the actual reference instead of its identifier.
34
+ #
35
+ # While you can use this to build up nested references within the object
36
+ # tree, it is recommended to persist only identifiers, and them provide
37
+ # helper methods to look up the actual references in the ObjectStore
38
+ # if needed. If you take this approach, Prawn::Document::Snapshot
39
+ # will probably work with your extension
40
+ #
41
+ def ref!(data, &block)
42
+ @store.ref(data, &block)
43
+ end
44
+
45
+ def page_content
46
+ @store[@page_content]
47
+ end
48
+
49
+ def current_page
50
+ @store[@current_page]
51
+ end
52
+
53
+ # Grabs the reference for the current page content
54
+ #
55
+ def page_content
56
+ @active_stamp_stream || @store[@page_content]
57
+ end
58
+
59
+ # Grabs the reference for the current page
60
+ #
61
+ def current_page
62
+ @active_stamp_dictionary || @store[@current_page]
63
+ end
30
64
 
31
65
  # Appends a raw string to the current page content.
32
66
  #
@@ -37,20 +71,20 @@ module Prawn
37
71
  # pdf.add_content("S") # stroke
38
72
  #
39
73
  def add_content(str)
40
- @page_content << str << "\n"
74
+ page_content << str << "\n"
41
75
  end
42
76
 
43
77
  # Add a new type to the current pages ProcSet
44
78
  #
45
79
  def proc_set(*types)
46
- @current_page.data[:ProcSet] ||= ref([])
47
- @current_page.data[:ProcSet].data |= types
80
+ current_page.data[:ProcSet] ||= ref!([])
81
+ current_page.data[:ProcSet].data |= types
48
82
  end
49
83
 
50
84
  # The Resources dictionary for the current page
51
85
  #
52
86
  def page_resources
53
- @current_page.data[:Resources] ||= {}
87
+ current_page.data[:Resources] ||= {}
54
88
  end
55
89
 
56
90
  # The Font dictionary for the current page
@@ -63,24 +97,28 @@ module Prawn
63
97
  #
64
98
  def page_xobjects
65
99
  page_resources[:XObject] ||= {}
66
- end
100
+ end
101
+
102
+ def page_ext_gstates
103
+ page_resources[:ExtGState] ||= {}
104
+ end
67
105
 
68
106
  # The Name dictionary (PDF spec 3.6.3) for this document. It is
69
107
  # lazily initialized, so that documents that do not need a name
70
108
  # dictionary do not incur the additional overhead.
71
109
  #
72
110
  def names
73
- @root.data[:Names] ||= ref(:Type => :Names)
111
+ @store.root.data[:Names] ||= ref!(:Type => :Names)
74
112
  end
75
113
 
76
114
  private
77
115
 
78
116
  def finish_page_content
79
- @header.draw if @header
80
- @footer.draw if @footer
117
+ @header.draw if defined?(@header) and @header
118
+ @footer.draw if defined?(@footer) and @footer
81
119
  add_content "Q"
82
- @page_content.compress_stream if compression_enabled?
83
- @page_content.data[:Length] = @page_content.stream.size
120
+ page_content.compress_stream if compression_enabled?
121
+ page_content.data[:Length] = page_content.stream.size
84
122
  end
85
123
 
86
124
  # raise the PDF version of the file we're going to generate.
@@ -91,39 +129,44 @@ module Prawn
91
129
  end
92
130
 
93
131
  # Write out the PDF Header, as per spec 3.4.1
132
+ #
94
133
  def render_header(output)
95
134
  # pdf version
96
135
  output << "%PDF-#{@version}\n"
97
136
 
98
137
  # 4 binary chars, as recommended by the spec
99
- output << "\xFF\xFF\xFF\xFF\n"
138
+ output << "%\xFF\xFF\xFF\xFF\n"
100
139
  end
101
140
 
102
141
  # Write out the PDF Body, as per spec 3.4.2
142
+ #
103
143
  def render_body(output)
104
- @objects.each do |ref|
144
+ @store.each do |ref|
105
145
  ref.offset = output.size
106
146
  output << ref.object
107
147
  end
108
148
  end
109
149
 
110
150
  # Write out the PDF Cross Reference Table, as per spec 3.4.3
151
+ #
111
152
  def render_xref(output)
112
153
  @xref_offset = output.size
113
154
  output << "xref\n"
114
- output << "0 #{@objects.size + 1}\n"
155
+ output << "0 #{@store.size + 1}\n"
115
156
  output << "0000000000 65535 f \n"
116
- @objects.each do |ref|
157
+ @store.each do |ref|
117
158
  output.printf("%010d", ref.offset)
118
159
  output << " 00000 n \n"
119
160
  end
120
161
  end
121
162
 
122
163
  # Write out the PDF Trailer, as per spec 3.4.4
164
+ #
123
165
  def render_trailer(output)
124
- trailer_hash = {:Size => @objects.size + 1,
125
- :Root => @root,
126
- :Info => @info}
166
+ trailer_hash = {:Size => @store.size + 1,
167
+ :Root => @store.root,
168
+ :Info => @store.info}
169
+ trailer_hash.merge!(@trailer) if @trailer
127
170
 
128
171
  output << "trailer\n"
129
172
  output << Prawn::PdfObject(trailer_hash) << "\n"
@@ -0,0 +1,71 @@
1
+ # encoding: utf-8
2
+
3
+ # snapshot.rb : Implements transactional rendering for Prawn
4
+ #
5
+ # Copyright August 2008, Brad Ediger. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+ require 'delegate'
9
+
10
+ module Prawn
11
+ class Document
12
+ module Snapshot
13
+
14
+ RollbackTransaction = Class.new(StandardError)
15
+
16
+ # Call this within a +transaction+ block to roll back the transaction and
17
+ # prevent any of its data from being rendered. You must reset the
18
+ # y-position yourself if you have performed any drawing operations that
19
+ # modify it.
20
+ def rollback
21
+ raise RollbackTransaction
22
+ end
23
+
24
+ # Run a block of drawing operations, to be completed atomically. If
25
+ # +rollback+ is called or a RollbackTransaction exception is raised
26
+ # inside the block, all actions taken inside the block will be rolled
27
+ # back (with the exception of y-position, which you must restore
28
+ # yourself).
29
+ #
30
+ # Returns true on success, or false if the transaction was rolled back.
31
+ def transaction
32
+ snap = take_snapshot
33
+ yield
34
+ true
35
+ rescue RollbackTransaction
36
+ restore_snapshot(snap)
37
+ false
38
+ end
39
+
40
+ private
41
+
42
+ # Takes a current snapshot of the document's state, sufficient to
43
+ # reconstruct it after it was amended.
44
+ def take_snapshot
45
+ {:page_content => Marshal.load(Marshal.dump(page_content)),
46
+ :current_page => Marshal.load(Marshal.dump(current_page)),
47
+ :page_kids => @store.pages.data[:Kids].map{|kid| kid.identifier},
48
+ :dests => Marshal.load(Marshal.dump(names.data[:Dests]))}
49
+ end
50
+
51
+ # Rolls the page state back to the state of the given snapshot.
52
+ def restore_snapshot(shot)
53
+ # Because these objects are referenced by identifier from the Pages
54
+ # dictionary, we can't just restore them over the current refs in
55
+ # page_content and current_page. We have to restore them over the old
56
+ # ones.
57
+ @page_content = shot[:page_content].identifier
58
+ page_content.replace shot[:page_content]
59
+
60
+ @current_page = shot[:current_page].identifier
61
+ current_page.replace shot[:current_page]
62
+
63
+ @store.pages.data[:Kids] = shot[:page_kids].map{|id| @store[id]}
64
+ @store.pages.data[:Count] = shot[:page_kids].size
65
+
66
+ names.data[:Dests] = shot[:dests]
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -23,6 +23,8 @@ module Prawn
23
23
  end
24
24
 
25
25
  module Text
26
+ # FIXME: requires documentation
27
+ #
26
28
  class Box #:nodoc:
27
29
  def initialize(text,options={})
28
30
  @document = options[:for]
@@ -54,6 +56,8 @@ module Prawn
54
56
  unless @overflow == :expand
55
57
  @document.y = y + @document.bounds.absolute_bottom - @height
56
58
  end
59
+
60
+ @excess_text
57
61
  end
58
62
 
59
63
  private
@@ -71,6 +75,9 @@ module Prawn
71
75
  when :ellipses
72
76
  @text[-3..-1] = "..." if @text.size > 3
73
77
  end
78
+ @excess_text = @document.naive_unwrap(lines[max_lines..-1].join)
79
+ else
80
+ @excess_text = ""
74
81
  end
75
82
  end
76
83
 
@@ -53,6 +53,9 @@ module Prawn
53
53
  output
54
54
  end
55
55
 
56
+ def naive_unwrap(string)
57
+ string.gsub(/(\S)\n/, '\1 ')
58
+ end
56
59
  end
57
60
  end
58
61
  end
@@ -43,8 +43,10 @@ module Prawn
43
43
  #
44
44
  # === Text Positioning Details:
45
45
  #
46
- # When using the :at parameter, Prawn will position your text by its
47
- # baseline, and flow along a single line.
46
+ # When using the :at parameter, Prawn will position your text by the
47
+ # left-most edge of its baseline, and flow along a single line. (This
48
+ # means that :align will not work)
49
+ #
48
50
  #
49
51
  # Otherwise, the text is positioned at font.ascender below the baseline,
50
52
  # making it easy to use this method within bounding boxes and spans.
@@ -79,6 +81,11 @@ module Prawn
79
81
  font.normalize_encoding!(text) unless @skip_encoding
80
82
 
81
83
  if options[:at]
84
+
85
+ if options[:align]
86
+ raise ArgumentError, "The :align option does not work with :at"
87
+ end
88
+
82
89
  x,y = translate(options[:at])
83
90
  font_size(options[:size]) { add_text_content(text,x,y,options) }
84
91
  else
@@ -15,6 +15,7 @@ require "prawn/document/span"
15
15
  require "prawn/document/text"
16
16
  require "prawn/document/annotations"
17
17
  require "prawn/document/destinations"
18
+ require "prawn/document/snapshot"
18
19
 
19
20
  module Prawn
20
21
 
@@ -58,13 +59,20 @@ module Prawn
58
59
  include Internals
59
60
  include Annotations
60
61
  include Destinations
62
+ include Snapshot
61
63
  include Prawn::Graphics
62
64
  include Prawn::Images
65
+ include Prawn::Stamp
63
66
 
64
- attr_accessor :y, :margin_box
65
- attr_reader :margins, :page_size, :page_layout
67
+ attr_accessor :margin_box
68
+ attr_reader :margins, :page_size, :page_layout, :y
66
69
  attr_writer :font_size
67
70
 
71
+
72
+ def self.extensions
73
+ @extensions ||= []
74
+ end
75
+
68
76
  # Creates and renders a PDF document.
69
77
  #
70
78
  # When using the implicit block form, Prawn will evaluate the block
@@ -102,6 +110,7 @@ module Prawn
102
110
  #
103
111
  # <tt>:page_size</tt>:: One of the Document::PageGeometry sizes [LETTER]
104
112
  # <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt>
113
+ # <tt>:margin</tt>:: Sets the margin on all sides in points [0.5 inch]
105
114
  # <tt>:left_margin</tt>:: Sets the left margin in points [0.5 inch]
106
115
  # <tt>:right_margin</tt>:: Sets the right margin in points [0.5 inch]
107
116
  # <tt>:top_margin</tt>:: Sets the top margin in points [0.5 inch]
@@ -111,7 +120,19 @@ module Prawn
111
120
  # <tt>:background</tt>:: An image path to be used as background on all pages [nil]
112
121
  # <tt>:info</tt>:: Generic hash allowing for custom metadata properties [nil]
113
122
  # <tt>:text_options</tt>:: A set of default options to be handed to text(). Be careful with this.
114
-
123
+ #
124
+ # Setting e.g. the :margin to 100 points and the :left_margin to 50 will result in margins
125
+ # of 100 points on every side except for the left, where it will be 50.
126
+ #
127
+ # The :margin can also be an array much like CSS shorthand:
128
+ #
129
+ # # Top and bottom are 20, left and right are 100.
130
+ # :margin => [20, 100]
131
+ # # Top is 50, left and right are 100, bottom is 20.
132
+ # :margin => [50, 100, 20]
133
+ # # Top is 10, right is 20, bottom is 30, left is 40.
134
+ # :margin => [10, 20, 30, 40]
135
+ #
115
136
  # Additionally, :page_size can be specified as a simple two value array giving
116
137
  # the width and height of the document you need in PDF Points.
117
138
  #
@@ -130,9 +151,11 @@ module Prawn
130
151
  # pdf = Prawn::Document.new(:background => "#{Prawn::BASEDIR}/data/images/pigs.jpg")
131
152
  #
132
153
  def initialize(options={},&block)
133
- Prawn.verify_options [:page_size, :page_layout, :left_margin,
154
+ Prawn.verify_options [:page_size, :page_layout, :margin, :left_margin,
134
155
  :right_margin, :top_margin, :bottom_margin, :skip_page_creation,
135
156
  :compress, :skip_encoding, :text_options, :background, :info], options
157
+
158
+ self.class.extensions.reverse_each { |e| extend e }
136
159
 
137
160
  options[:info] ||= {}
138
161
  options[:info][:Creator] ||= "Prawn"
@@ -145,23 +168,28 @@ module Prawn
145
168
  end
146
169
 
147
170
  @version = 1.3
148
- @objects = []
149
- @info = ref(options[:info])
150
- @pages = ref(:Type => :Pages, :Count => 0, :Kids => [])
151
- @root = ref(:Type => :Catalog, :Pages => @pages)
152
- @page_size = options[:page_size] || "LETTER"
153
- @page_layout = options[:page_layout] || :portrait
154
- @compress = options[:compress] || false
155
- @skip_encoding = options[:skip_encoding]
156
- @background = options[:background]
157
- @font_size = 12
171
+ @store = ObjectStore.new(options[:info])
172
+ @trailer = {}
173
+
174
+ @page_size = options[:page_size] || "LETTER"
175
+ @page_layout = options[:page_layout] || :portrait
176
+ @compress = options[:compress] || false
177
+ @skip_encoding = options[:skip_encoding]
178
+ @background = options[:background]
179
+ @font_size = 12
180
+ @page_content = nil
181
+ @bounding_box = nil
182
+ @margin_box = nil
158
183
 
159
184
  @text_options = options[:text_options] || {}
185
+
186
+ apply_margin_option(options) if options[:margin]
160
187
 
161
- @margins = { :left => options[:left_margin] || 36,
162
- :right => options[:right_margin] || 36,
163
- :top => options[:top_margin] || 36,
164
- :bottom => options[:bottom_margin] || 36 }
188
+ default_margin = 36 # 0.5 inch
189
+ @margins = { :left => options[:left_margin] || default_margin,
190
+ :right => options[:right_margin] || default_margin,
191
+ :top => options[:top_margin] || default_margin,
192
+ :bottom => options[:bottom_margin] || default_margin }
165
193
 
166
194
  generate_margin_box
167
195
 
@@ -182,22 +210,25 @@ module Prawn
182
210
  # pdf.start_new_page #=> Starts new page keeping current values
183
211
  # pdf.start_new_page(:size => "LEGAL", :layout => :landscape)
184
212
  # pdf.start_new_page(:left_margin => 50, :right_margin => 50)
213
+ # pdf.start_new_page(:margin => 100)
185
214
  #
186
215
  def start_new_page(options = {})
187
216
  @page_size = options[:size] if options[:size]
188
217
  @page_layout = options[:layout] if options[:layout]
218
+
219
+ apply_margin_option(options) if options[:margin]
189
220
 
190
221
  [:left,:right,:top,:bottom].each do |side|
191
- if options[:"#{side}_margin"]
192
- @margins[side] = options[:"#{side}_margin"]
222
+ if margin = options[:"#{side}_margin"]
223
+ @margins[side] = margin
193
224
  end
194
225
  end
195
226
 
196
227
  finish_page_content if @page_content
197
228
  build_new_page_content
198
229
 
199
- @pages.data[:Kids] << @current_page
200
- @pages.data[:Count] += 1
230
+ @store.pages.data[:Kids] << current_page
231
+ @store.pages.data[:Count] += 1
201
232
 
202
233
  add_content "q"
203
234
 
@@ -214,7 +245,12 @@ module Prawn
214
245
  # pdf.page_count #=> 4
215
246
  #
216
247
  def page_count
217
- @pages.data[:Count]
248
+ @store.pages.data[:Count]
249
+ end
250
+
251
+ def y=(new_y)
252
+ @y = new_y
253
+ bounds.update_height
218
254
  end
219
255
 
220
256
  # The current y drawing position relative to the innermost bounding box,
@@ -224,6 +260,13 @@ module Prawn
224
260
  y - bounds.absolute_bottom
225
261
  end
226
262
 
263
+
264
+ # Moves to the specified y position in relative terms to the bottom margin.
265
+ #
266
+ def move_cursor_to(new_y)
267
+ self.y = new_y + bounds.absolute_bottom
268
+ end
269
+
227
270
  # Renders the PDF document to string, useful for example in a Rails
228
271
  # application where you want to stream out the PDF to a web browser:
229
272
  #
@@ -376,6 +419,67 @@ module Prawn
376
419
  fields.each { |f| send("#{f}=", stored[f]) }
377
420
  end
378
421
 
422
+ # Raised if group() is called with a block that is too big to be
423
+ # rendered in the current context.
424
+ #
425
+ CannotGroup = Class.new(StandardError)
426
+
427
+ # Attempts to group the given block vertically within the current context.
428
+ # First attempts to render it in the current position on the current page.
429
+ # If that attempt overflows, it is tried anew after starting a new context
430
+ # (page or column).
431
+ #
432
+ # Raises CannotGroup if the provided content is too large to fit alone in
433
+ # the current page or column.
434
+ #
435
+ def group(second_attempt=false)
436
+ old_bounding_box = @bounding_box
437
+ @bounding_box = SimpleDelegator.new(@bounding_box)
438
+
439
+ def @bounding_box.move_past_bottom
440
+ raise RollbackTransaction
441
+ end
442
+
443
+ success = transaction { yield }
444
+
445
+ unless success
446
+ raise CannotGroup if second_attempt
447
+ old_bounding_box.move_past_bottom
448
+ group(second_attempt=true) { yield }
449
+ end
450
+
451
+ @bounding_box = old_bounding_box
452
+ end
453
+
454
+ # Specify a template for page numbering. This should be called
455
+ # towards the end of document creation, after all your content is already in
456
+ # place. In your template string, <page> refers to the current page, and
457
+ # <total> refers to the total amount of pages in the doucment.
458
+ #
459
+ # Example:
460
+ #
461
+ # Prawn::Document.generate("page_with_numbering.pdf") do
462
+ # text "Hai"
463
+ # start_new_page
464
+ # text "bai"
465
+ # start_new_page
466
+ # text "-- Hai again"
467
+ # number_pages "<page> in a total of <total>", [bounds.right - 50, 0]
468
+ # end
469
+ def number_pages(string, position)
470
+ page_count.times do |i|
471
+ go_to_page(i)
472
+ str = string.gsub("<page>","#{i+1}").gsub("<total>","#{page_count}")
473
+ text str, :at => position
474
+ end
475
+ end
476
+
477
+ def go_to_page(k) # :nodoc:
478
+ jump_to = @store.pages.data[:Kids][k]
479
+ @current_page = jump_to.identifier
480
+ @page_content = jump_to.data[:Contents].identifier
481
+ end
482
+
379
483
  # Returns true if content streams will be compressed before rendering,
380
484
  # false otherwise
381
485
  #
@@ -392,10 +496,11 @@ module Prawn
392
496
  @page_content = ref(:Length => 0)
393
497
 
394
498
  @current_page = ref(:Type => :Page,
395
- :Parent => @pages,
499
+ :Parent => @store.pages,
396
500
  :MediaBox => page_dimensions,
397
- :Contents => @page_content)
501
+ :Contents => page_content)
398
502
  update_colors
503
+ undash if dashed?
399
504
  end
400
505
 
401
506
  def generate_margin_box
@@ -413,6 +518,17 @@ module Prawn
413
518
  # when the bounding box exits.
414
519
  @bounding_box = @margin_box if old_margin_box == @bounding_box
415
520
  end
521
+
522
+ def apply_margin_option(options)
523
+ # Treat :margin as CSS shorthand with 1-4 values.
524
+ margin = Array(options[:margin])
525
+ positions = { 4 => [0,1,2,3], 3 => [0,1,2,1],
526
+ 2 => [0,1,0,1], 1 => [0,0,0,0] }[margin.length]
527
+
528
+ [:top, :right, :bottom, :left].zip(positions).each do |p,i|
529
+ options[:"#{p}_margin"] ||= margin[i]
530
+ end
531
+ end
416
532
 
417
533
  end
418
534
  end
data/lib/prawn/errors.rb CHANGED
@@ -45,5 +45,17 @@ module Prawn
45
45
  # type. This can either a completely unsupported format, or a dialect of a
46
46
  # supported format (ie. some types of PNG)
47
47
  UnsupportedImageType = Class.new(StandardError)
48
+
49
+ # This error is raised when a named element has alredy been
50
+ # created. For example, in the stamp module, stamps must have
51
+ # unique names within a document
52
+ NameTaken = Class.new(StandardError)
53
+
54
+ # This error is raised when a name is not a valid format
55
+ InvalidName = Class.new(StandardError)
56
+
57
+ # This error is raised when an object is attempted to be
58
+ # referenced by name, but no such name is associated with an object
59
+ UndefinedObjectName = Class.new(StandardError)
48
60
  end
49
61
  end
@@ -95,7 +95,7 @@ module Prawn
95
95
  private
96
96
 
97
97
  def register(subset)
98
- @document.ref(:Type => :Font,
98
+ @document.ref!(:Type => :Font,
99
99
  :Subtype => :Type1,
100
100
  :BaseFont => name.to_sym,
101
101
  :Encoding => :WinAnsiEncoding)