fullcirclegroup-fullcirclegroup-prawn 0.2.99.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. data/COPYING +340 -0
  2. data/LICENSE +56 -0
  3. data/README +47 -0
  4. data/Rakefile +76 -0
  5. data/data/fonts/Activa.ttf +0 -0
  6. data/data/fonts/Chalkboard.ttf +0 -0
  7. data/data/fonts/Courier-Bold.afm +342 -0
  8. data/data/fonts/Courier-BoldOblique.afm +342 -0
  9. data/data/fonts/Courier-Oblique.afm +342 -0
  10. data/data/fonts/Courier.afm +342 -0
  11. data/data/fonts/DejaVuSans.ttf +0 -0
  12. data/data/fonts/Dustismo_Roman.ttf +0 -0
  13. data/data/fonts/Helvetica-Bold.afm +2827 -0
  14. data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
  15. data/data/fonts/Helvetica-Oblique.afm +3051 -0
  16. data/data/fonts/Helvetica.afm +3051 -0
  17. data/data/fonts/MustRead.html +19 -0
  18. data/data/fonts/Symbol.afm +213 -0
  19. data/data/fonts/Times-Bold.afm +2588 -0
  20. data/data/fonts/Times-BoldItalic.afm +2384 -0
  21. data/data/fonts/Times-Italic.afm +2667 -0
  22. data/data/fonts/Times-Roman.afm +2419 -0
  23. data/data/fonts/ZapfDingbats.afm +225 -0
  24. data/data/fonts/comicsans.ttf +0 -0
  25. data/data/fonts/gkai00mp.ttf +0 -0
  26. data/data/images/arrow.png +0 -0
  27. data/data/images/arrow2.png +0 -0
  28. data/data/images/barcode_issue.png +0 -0
  29. data/data/images/dice.alpha +0 -0
  30. data/data/images/dice.dat +0 -0
  31. data/data/images/dice.png +0 -0
  32. data/data/images/page_white_text.alpha +0 -0
  33. data/data/images/page_white_text.dat +0 -0
  34. data/data/images/page_white_text.png +0 -0
  35. data/data/images/pigs.jpg +0 -0
  36. data/data/images/rails.dat +0 -0
  37. data/data/images/rails.png +0 -0
  38. data/data/images/ruport.png +0 -0
  39. data/data/images/ruport_data.dat +0 -0
  40. data/data/images/ruport_transparent.png +0 -0
  41. data/data/images/ruport_type0.png +0 -0
  42. data/data/images/stef.jpg +0 -0
  43. data/data/images/web-links.dat +1 -0
  44. data/data/images/web-links.png +0 -0
  45. data/data/shift_jis_text.txt +1 -0
  46. data/examples/addressbook.csv +6 -0
  47. data/examples/alignment.rb +16 -0
  48. data/examples/bounding_boxes.rb +30 -0
  49. data/examples/canvas.rb +12 -0
  50. data/examples/cell.rb +38 -0
  51. data/examples/chinese_text_wrapping.rb +17 -0
  52. data/examples/currency.csv +1834 -0
  53. data/examples/curves.rb +10 -0
  54. data/examples/family_based_styling.rb +21 -0
  55. data/examples/fancy_table.rb +61 -0
  56. data/examples/flowing_text_with_header_and_footer.rb +72 -0
  57. data/examples/font_size.rb +27 -0
  58. data/examples/hexagon.rb +14 -0
  59. data/examples/image.rb +23 -0
  60. data/examples/image2.rb +13 -0
  61. data/examples/image_flow.rb +34 -0
  62. data/examples/kerning.rb +27 -0
  63. data/examples/lazy_bounding_boxes.rb +19 -0
  64. data/examples/line.rb +31 -0
  65. data/examples/multi_page_layout.rb +14 -0
  66. data/examples/page_geometry.rb +28 -0
  67. data/examples/png_types.rb +23 -0
  68. data/examples/polygons.rb +16 -0
  69. data/examples/position_by_baseline.rb +26 -0
  70. data/examples/ruport_formatter.rb +50 -0
  71. data/examples/ruport_helpers.rb +18 -0
  72. data/examples/russian_boxes.rb +34 -0
  73. data/examples/simple_text.rb +15 -0
  74. data/examples/simple_text_ttf.rb +16 -0
  75. data/examples/sjis.rb +21 -0
  76. data/examples/span.rb +27 -0
  77. data/examples/table.rb +47 -0
  78. data/examples/table_header_color.rb +16 -0
  79. data/examples/text_flow.rb +65 -0
  80. data/examples/top_and_bottom_cells.rb +40 -0
  81. data/examples/utf8.rb +12 -0
  82. data/lib/prawn.rb +67 -0
  83. data/lib/prawn/compatibility.rb +46 -0
  84. data/lib/prawn/document.rb +309 -0
  85. data/lib/prawn/document/bounding_box.rb +362 -0
  86. data/lib/prawn/document/internals.rb +113 -0
  87. data/lib/prawn/document/page_geometry.rb +79 -0
  88. data/lib/prawn/document/span.rb +47 -0
  89. data/lib/prawn/document/table.rb +350 -0
  90. data/lib/prawn/document/text.rb +196 -0
  91. data/lib/prawn/errors.rb +48 -0
  92. data/lib/prawn/font.rb +356 -0
  93. data/lib/prawn/font/cmap.rb +59 -0
  94. data/lib/prawn/font/metrics.rb +378 -0
  95. data/lib/prawn/font/wrapping.rb +47 -0
  96. data/lib/prawn/graphics.rb +252 -0
  97. data/lib/prawn/graphics/cell.rb +264 -0
  98. data/lib/prawn/graphics/color.rb +132 -0
  99. data/lib/prawn/images.rb +336 -0
  100. data/lib/prawn/images/jpg.rb +45 -0
  101. data/lib/prawn/images/png.rb +199 -0
  102. data/lib/prawn/pdf_object.rb +73 -0
  103. data/lib/prawn/reference.rb +56 -0
  104. data/spec/bounding_box_spec.rb +141 -0
  105. data/spec/document_spec.rb +181 -0
  106. data/spec/font_spec.rb +141 -0
  107. data/spec/graphics_spec.rb +209 -0
  108. data/spec/images_spec.rb +68 -0
  109. data/spec/jpg_spec.rb +25 -0
  110. data/spec/metrics_spec.rb +62 -0
  111. data/spec/pdf_object_spec.rb +112 -0
  112. data/spec/png_spec.rb +196 -0
  113. data/spec/reference_spec.rb +42 -0
  114. data/spec/spec_helper.rb +23 -0
  115. data/spec/table_spec.rb +179 -0
  116. data/spec/text_spec.rb +135 -0
  117. metadata +181 -0
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+
3
+ if RUBY_VERSION < "1.9"
4
+ require "strscan"
5
+
6
+ class String #:nodoc:
7
+ alias_method :lines, :to_a
8
+
9
+ def each_char
10
+ scanner, char = StringScanner.new(self), /./mu
11
+ loop { yield(scanner.scan(char) || break) }
12
+ end
13
+
14
+ end
15
+
16
+ class File #:nodoc:
17
+ def self.read_binary(file)
18
+ File.open(file,"rb") { |f| f.read }
19
+ end
20
+ end
21
+
22
+ def ruby_18 #:nodoc:
23
+ yield
24
+ end
25
+
26
+ def ruby_19 #:nodoc:
27
+ false
28
+ end
29
+
30
+ else
31
+
32
+ class File #:nodoc:
33
+ def self.read_binary(file)
34
+ File.open(file,"rb:BINARY") { |f| f.read }
35
+ end
36
+ end
37
+
38
+ def ruby_18 #:nodoc:
39
+ false
40
+ end
41
+
42
+ def ruby_19 #:nodoc:
43
+ yield
44
+ end
45
+
46
+ end
@@ -0,0 +1,309 @@
1
+ # encoding: utf-8
2
+
3
+ # document.rb : Implements PDF document generation for Prawn
4
+ #
5
+ # Copyright April 2008, Gregory Brown. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+ require "stringio"
10
+ require "prawn/document/page_geometry"
11
+ require "prawn/document/bounding_box"
12
+ require "prawn/document/text"
13
+ require "prawn/document/table"
14
+ require "prawn/document/internals"
15
+ require "prawn/document/span"
16
+ require "prawn/document/annotations"
17
+ require "prawn/document/destinations"
18
+
19
+ module Prawn
20
+ class Document
21
+
22
+ include Prawn::Document::Internals
23
+ include Prawn::Document::Annotations
24
+ include Prawn::Document::Destinations
25
+ include Prawn::Graphics
26
+ include Prawn::Images
27
+ include Text
28
+ include PageGeometry
29
+
30
+ attr_accessor :y, :margin_box
31
+ attr_reader :margins, :page_size, :page_layout
32
+
33
+ # Creates and renders a PDF document.
34
+ #
35
+ # When using the implicit block form, Prawn will evaluate the block
36
+ # within an instance of Prawn::Document, simplifying your syntax.
37
+ # However, please note that you will not be able to reference variables
38
+ # from the enclosing scope within this block.
39
+ #
40
+ # # Using implicit block form and rendering to a file
41
+ # Prawn::Document.generate "foo.pdf" do
42
+ # font "Times-Roman"
43
+ # text "Hello World", :at => [200,720], :size => 32
44
+ # end
45
+ #
46
+ # If you need to access your local and instance variables, use the explicit
47
+ # block form shown below. In this case, Prawn yields an instance of
48
+ # PDF::Document and the block is an ordinary closure:
49
+ #
50
+ # # Using explicit block form and rendering to a file
51
+ # content = "Hello World"
52
+ # Prawn::Document.generate "foo.pdf" do |pdf|
53
+ # pdf.font "Times-Roman"
54
+ # pdf.text content, :at => [200,720], :size => 32
55
+ # end
56
+ #
57
+ def self.generate(filename,options={},&block)
58
+ pdf = Prawn::Document.new(options,&block)
59
+ pdf.render_file(filename)
60
+ end
61
+
62
+ # Creates a new PDF Document. The following options are available:
63
+ #
64
+ # <tt>:page_size</tt>:: One of the Document::PageGeometry::SIZES [LETTER]
65
+ # <tt>:page_layout</tt>:: Either <tt>:portrait</tt> or <tt>:landscape</tt>
66
+ # <tt>:left_margin</tt>:: Sets the left margin in points [ 0.5 inch]
67
+ # <tt>:right_margin</tt>:: Sets the right margin in points [ 0.5 inch]
68
+ # <tt>:top_margin</tt>:: Sets the top margin in points [ 0.5 inch]
69
+ # <tt>:bottom_margin</tt>:: Sets the bottom margin in points [0.5 inch]
70
+ # <tt>:skip_page_creation</tt>:: Creates a document without starting the first page [false]
71
+ # <tt>:compress</tt>:: Compresses content streams before rendering them [false]
72
+ # <tt>:background</tt>:: An image path to be used as background on all pages [nil]
73
+ #
74
+ # Usage:
75
+ #
76
+ # # New document, US Letter paper, portrait orientation
77
+ # pdf = Prawn::Document.new
78
+ #
79
+ # # New document, A4 paper, landscaped
80
+ # pdf = Prawn::Document.new(:page_size => "A4", :page_layout => :landscape)
81
+ #
82
+ # # New document, with background
83
+ # pdf = Prawn::Document.new(:background => "#{Prawn::BASEDIR}/data/images/pigs.jpg")
84
+ #
85
+ def initialize(options={},&block)
86
+ Prawn.verify_options [:page_size, :page_layout, :left_margin,
87
+ :right_margin, :top_margin, :bottom_margin, :skip_page_creation,
88
+ :compress, :skip_encoding, :text_options, :background ], options
89
+
90
+ @objects = []
91
+ @info = ref(:Creator => "Prawn", :Producer => "Prawn")
92
+ @pages = ref(:Type => :Pages, :Count => 0, :Kids => [])
93
+ @root = ref(:Type => :Catalog, :Pages => @pages)
94
+ @page_size = options[:page_size] || "LETTER"
95
+ @page_layout = options[:page_layout] || :portrait
96
+ @compress = options[:compress] || false
97
+ @skip_encoding = options[:skip_encoding]
98
+ @background = options[:background]
99
+
100
+ text_options.update(options[:text_options] || {})
101
+
102
+ @margins = { :left => options[:left_margin] || 36,
103
+ :right => options[:right_margin] || 36,
104
+ :top => options[:top_margin] || 36,
105
+ :bottom => options[:bottom_margin] || 36 }
106
+
107
+ generate_margin_box
108
+
109
+ @bounding_box = @margin_box
110
+
111
+ start_new_page unless options[:skip_page_creation]
112
+
113
+ if block
114
+ block.arity < 1 ? instance_eval(&block) : block[self]
115
+ end
116
+ end
117
+
118
+ # Creates and advances to a new page in the document.
119
+ #
120
+ # Page size, margins, and layout can also be set when generating a
121
+ # new page. These values will become the new defaults for page creation
122
+ #
123
+ # pdf.start_new_page(:size => "LEGAL", :layout => :landscape)
124
+ # pdf.start_new_page(:left_margin => 50, :right_margin => 50)
125
+ #
126
+ def start_new_page(options = {})
127
+ @page_size = options[:size] if options[:size]
128
+ @page_layout = options[:layout] if options[:layout]
129
+
130
+ [:left,:right,:top,:bottom].each do |side|
131
+ if options[:"#{side}_margin"]
132
+ @margins[side] = options[:"#{side}_margin"]
133
+ end
134
+ end
135
+
136
+ finish_page_content if @page_content
137
+ build_new_page_content
138
+
139
+ @pages.data[:Kids] << @current_page
140
+ @pages.data[:Count] += 1
141
+
142
+ add_content "q"
143
+
144
+ @y = @bounding_box.absolute_top
145
+
146
+ image(@background, :at => [0,@y]) if @background
147
+ end
148
+
149
+ # Returns the number of pages in the document
150
+ #
151
+ # pdf = Prawn::Document.new
152
+ # pdf.page_count #=> 1
153
+ # 3.times { pdf.start_new_page }
154
+ # pdf.page_count #=> 4
155
+ #
156
+ def page_count
157
+ @pages.data[:Count]
158
+ end
159
+
160
+ # The current y drawing position relative to the innermost bounding box,
161
+ # or to the page margins at the top level.
162
+ #
163
+ def cursor
164
+ y - bounds.absolute_bottom
165
+ end
166
+
167
+ # Renders the PDF document to string
168
+ #
169
+ def render
170
+ output = StringIO.new
171
+ finish_page_content
172
+
173
+ render_header(output)
174
+ render_body(output)
175
+ render_xref(output)
176
+ render_trailer(output)
177
+ str = output.string
178
+ str.force_encoding("ASCII-8BIT") if str.respond_to?(:force_encoding)
179
+ str
180
+ end
181
+
182
+ # Renders the PDF document to file.
183
+ #
184
+ # pdf.render_file "foo.pdf"
185
+ #
186
+ def render_file(filename)
187
+ Kernel.const_defined?("Encoding") ? mode = "wb:ASCII-8BIT" : mode = "wb"
188
+ File.open(filename,mode) { |f| f << render }
189
+ end
190
+
191
+ # Returns the current BoundingBox object, which is by default
192
+ # the box represented by the margin box. When called from within
193
+ # a <tt>bounding_box</tt> block, the box defined by that call will
194
+ # be used.
195
+ #
196
+ def bounds
197
+ @bounding_box
198
+ end
199
+
200
+ # Sets Document#bounds to the BoundingBox provided. If you don't know
201
+ # why you'd need to do this, chances are, you can ignore this feature
202
+ #
203
+ def bounds=(bounding_box)
204
+ @bounding_box = bounding_box
205
+ end
206
+
207
+ # Moves up the document by n points
208
+ #
209
+ def move_up(n)
210
+ self.y += n
211
+ end
212
+
213
+ # Moves down the document by n point
214
+ #
215
+ def move_down(n)
216
+ self.y -= n
217
+ end
218
+
219
+ # Moves down the document and then executes a block.
220
+ #
221
+ # pdf.text "some text"
222
+ # pdf.pad_top(100) do
223
+ # pdf.text "This is 100 points below the previous line of text"
224
+ # end
225
+ # pdf.text "This text appears right below the previous line of text"
226
+ #
227
+ def pad_top(y)
228
+ move_down(y)
229
+ yield
230
+ end
231
+
232
+ # Executes a block then moves down the document
233
+ #
234
+ # pdf.text "some text"
235
+ # pdf.pad_bottom(100) do
236
+ # pdf.text "This text appears right below the previous line of text"
237
+ # end
238
+ # pdf.text "This is 100 points below the previous line of text"
239
+ #
240
+ def pad_bottom(y)
241
+ yield
242
+ move_down(y)
243
+ end
244
+
245
+ # Moves down the document by y, executes a block, then moves down the
246
+ # document by y again.
247
+ #
248
+ # pdf.text "some text"
249
+ # pdf.pad(100) do
250
+ # pdf.text "This is 100 points below the previous line of text"
251
+ # end
252
+ # pdf.text "This is 100 points below the previous line of text"
253
+ #
254
+ def pad(y)
255
+ move_down(y)
256
+ yield
257
+ move_down(y)
258
+ end
259
+
260
+ def mask(*fields) # :nodoc:
261
+ # Stores the current state of the named attributes, executes the block, and
262
+ # then restores the original values after the block has executed.
263
+ # -- I will remove the nodoc if/when this feature is a little less hacky
264
+ stored = {}
265
+ fields.each { |f| stored[f] = send(f) }
266
+ yield
267
+ fields.each { |f| send("#{f}=", stored[f]) }
268
+ end
269
+
270
+ # Returns true if content streams will be compressed before rendering,
271
+ # false otherwise
272
+ #
273
+ def compression_enabled?
274
+ !!@compress
275
+ end
276
+
277
+ private
278
+
279
+ # See Prawn::Document::Internals for low-level PDF functions
280
+
281
+ def build_new_page_content
282
+ generate_margin_box
283
+ @page_content = ref(:Length => 0)
284
+
285
+ @current_page = ref(:Type => :Page,
286
+ :Parent => @pages,
287
+ :MediaBox => page_dimensions,
288
+ :Contents => @page_content)
289
+ font.add_to_current_page if @font
290
+ update_colors
291
+ end
292
+
293
+ def generate_margin_box
294
+ old_margin_box = @margin_box
295
+ @margin_box = BoundingBox.new(
296
+ self,
297
+ [ @margins[:left], page_dimensions[-1] - @margins[:top] ] ,
298
+ :width => page_dimensions[-2] - (@margins[:left] + @margins[:right]),
299
+ :height => page_dimensions[-1] - (@margins[:top] + @margins[:bottom])
300
+ )
301
+
302
+ # update bounding box if not flowing from the previous page
303
+ # FIXME: This may have a bug where the old margin is restored
304
+ # when the bounding box exits.
305
+ @bounding_box = @margin_box if old_margin_box == @bounding_box
306
+ end
307
+
308
+ end
309
+ end
@@ -0,0 +1,362 @@
1
+ # encoding: utf-8
2
+
3
+ module Prawn
4
+ class Document
5
+
6
+ # A bounding box serves two important purposes:
7
+ # * Provide bounds for flowing text, starting at a given point
8
+ # * Translate the origin (0,0) for graphics primitives, for the purposes
9
+ # of simplifying coordinate math.
10
+ #
11
+ # When flowing text, the usage of a bounding box is simple. Text will
12
+ # begin at the point specified, flowing the width of the bounding box.
13
+ # After the block exits, the text drawing position will be moved to
14
+ # the bottom of the bounding box (y - height). If flowing text exceeds
15
+ # the height of the bounding box, the text will be continued on the next
16
+ # page, starting again at the top-left corner of the bounding box.
17
+ #
18
+ # pdf.bounding_box([100,500], :width => 100, :height => 300) do
19
+ # pdf.text "This text will flow in a very narrow box starting" +
20
+ # "from [100,500]. The pointer will then be moved to [100,200]" +
21
+ # "and return to the margin_box"
22
+ # end
23
+ #
24
+ # When translating coordinates, the idea is to allow the user to draw
25
+ # relative to the origin, and then translate their drawing to a specified
26
+ # area of the document, rather than adjust all their drawing coordinates
27
+ # to match this new region.
28
+ #
29
+ # Take for example two triangles which share one point, drawn from the
30
+ # origin:
31
+ #
32
+ # pdf.polygon [0,250], [0,0], [150,100]
33
+ # pdf.polygon [100,0], [150,100], [200,0]
34
+ #
35
+ # It would be easy enough to translate these triangles to another point,
36
+ # e.g [200,200]
37
+ #
38
+ # pdf.polygon [200,450], [200,200], [350,300]
39
+ # pdf.polygon [300,200], [350,300], [400,200]
40
+ #
41
+ # However, each time you want to move the drawing, you'd need to alter
42
+ # every point in the drawing calls, which as you might imagine, can become
43
+ # tedious.
44
+ #
45
+ # If instead, we think of the drawing as being bounded by a box, we can
46
+ # see that the image is 200 points wide by 250 points tall.
47
+ #
48
+ # To translate it to a new origin, we simply select a point at (x,y+height)
49
+ #
50
+ # Using the [200,200] example:
51
+ #
52
+ # pdf.bounding_box([200,450], :width => 200, :height => 250) do
53
+ # pdf.polygon [0,250], [0,0], [150,100]
54
+ # pdf.polygon [100,0], [150,100], [200,0]
55
+ # end
56
+ #
57
+ # Notice that the drawing is still relative to the origin. If we want to
58
+ # move this drawing around the document, we simply need to recalculate the
59
+ # top-left corner of the rectangular bounding-box, and all of our graphics
60
+ # calls remain unmodified.
61
+ #
62
+ # By default, bounding boxes are specified relative to the document's
63
+ # margin_box (which is itself a bounding box). You can also nest bounding
64
+ # boxes, allowing you to build components which are relative to each other
65
+ #
66
+ # pdf.bouding_box([200,450], :width => 200, :height => 250) do
67
+ # pdf.bounding_box([50,200], :width => 50, :height => 50) do
68
+ # # a 50x50 bounding box that starts 50 pixels left and 50 pixels down
69
+ # # the parent bounding box.
70
+ # end
71
+ # end
72
+ #
73
+ # If you wish to position the bounding boxes at absolute coordinates rather
74
+ # than relative to the margins or other bounding boxes, you can use canvas()
75
+ #
76
+ # pdf.canvas do
77
+ # pdf.bounding_box([200,450], :width => 200, :height => 250) do
78
+ # # positioned at 'real' (200,450)
79
+ # end
80
+ # end
81
+ #
82
+ # Of course, if you use canvas, you will be responsible for ensuring that
83
+ # you remain within the printable area of your document.
84
+ #
85
+ def bounding_box(*args, &block)
86
+ init_bounding_box(block) do |_|
87
+ translate!(args[0])
88
+ @bounding_box = BoundingBox.new(self, *args)
89
+ end
90
+ end
91
+
92
+
93
+ # A LazyBoundingBox is simply a BoundingBox with an action tied to it to be
94
+ # executed later. The lazy_bounding_box method takes the same arguments as
95
+ # bounding_box, but returns a LazyBoundingBox object instead of executing
96
+ # the code block directly.
97
+ #
98
+ # You can then call LazyBoundingBox#draw at any time (or multiple times if
99
+ # you wish), and the contents of the block will then be run. This can be
100
+ # useful for assembling repeating page elements or reusable components.
101
+ #
102
+ # file = "lazy_bounding_boxes.pdf"
103
+ # Prawn::Document.generate(file, :skip_page_creation => true) do
104
+ # point = [bounds.right-50, bounds.bottom + 25]
105
+ # page_counter = lazy_bounding_box(point, :width => 50) do
106
+ # text "Page: #{page_count}"
107
+ # end
108
+ #
109
+ # 10.times do
110
+ # start_new_page
111
+ # text "Some text"
112
+ # page_counter.draw
113
+ # end
114
+ # end
115
+ #
116
+ def lazy_bounding_box(*args,&block)
117
+ translate!(args[0])
118
+ box = LazyBoundingBox.new(self,*args)
119
+ box.action(&block)
120
+ return box
121
+ end
122
+
123
+ # A shortcut to produce a bounding box which is mapped to the document's
124
+ # absolute coordinates, regardless of how things are nested or margin sizes.
125
+ #
126
+ # pdf.canvas do
127
+ # pdf.line pdf.bounds.bottom_left, pdf.bounds.top_right
128
+ # end
129
+ #
130
+ def canvas(&block)
131
+ init_bounding_box(block, :hold_position => true) do |_|
132
+ @bounding_box = BoundingBox.new(self, [0,page_dimensions[3]],
133
+ :width => page_dimensions[2],
134
+ :height => page_dimensions[3]
135
+ )
136
+ end
137
+ end
138
+
139
+ # A bounding box with the same dimensions of its parents, minus a margin
140
+ # on all sides
141
+ #
142
+ def padded_box(margin, &block)
143
+ bounding_box [bounds.left + margin, bounds.top - margin],
144
+ :width => bounds.width - (margin * 2),
145
+ :height => bounds.height - (margin * 2), &block
146
+ end
147
+
148
+ # A header is a LazyBoundingBox drawn relative to the margins that can be
149
+ # repeated on every page of the document.
150
+ #
151
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
152
+ # width and height are used.
153
+ #
154
+ # header margin_box.top_left do
155
+ # text "Here's My Fancy Header", :size => 25, :align => :center
156
+ # stroke_horizontal_rule
157
+ # end
158
+ #
159
+ def header(top_left,options={},&block)
160
+ @header = repeating_page_element(top_left,options,&block)
161
+ end
162
+
163
+ # A footer is a LazyBoundingBox drawn relative to the margins that can be
164
+ # repeated on every page of the document.
165
+ #
166
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
167
+ # width and height are used.
168
+ #
169
+ # footer [margin_box.left, margin_box.bottom + 25] do
170
+ # stroke_horizontal_rule
171
+ # text "And here's a sexy footer", :size => 16
172
+ # end
173
+ #
174
+ def footer(top_left,options={},&block)
175
+ @footer = repeating_page_element(top_left,options,&block)
176
+ end
177
+
178
+ private
179
+
180
+ def init_bounding_box(user_block, options={}, &init_block)
181
+ parent_box = @bounding_box
182
+
183
+ init_block.call(parent_box)
184
+
185
+ self.y = @bounding_box.absolute_top
186
+ user_block.call
187
+ self.y = @bounding_box.absolute_bottom unless options[:hold_position]
188
+
189
+ @bounding_box = parent_box
190
+ end
191
+
192
+ def repeating_page_element(top_left,options={},&block)
193
+ r = LazyBoundingBox.new(self, translate(top_left),
194
+ :width => options[:width] || margin_box.width,
195
+ :height => options[:height] || margin_box.height )
196
+ r.action(&block)
197
+ return r
198
+ end
199
+
200
+ class BoundingBox
201
+
202
+ def initialize(parent, point, options={}) #:nodoc:
203
+ @parent = parent
204
+ @x, @y = point
205
+ @width, @height = options[:width], options[:height]
206
+ end
207
+
208
+ # The translated origin (x,y-height) which describes the location
209
+ # of the bottom left corner of the bounding box
210
+ #
211
+ def anchor
212
+ [@x, @y - height]
213
+ end
214
+
215
+ # Relative left x-coordinate of the bounding box. (Always 0)
216
+ #
217
+ def left
218
+ 0
219
+ end
220
+
221
+ # Relative right x-coordinate of the bounding box. (Equal to the box width)
222
+ #
223
+ def right
224
+ @width
225
+ end
226
+
227
+ # Relative top y-coordinate of the bounding box. (Equal to the box height)
228
+ #
229
+ def top
230
+ height
231
+ end
232
+
233
+ # Relative bottom y-coordinate of the bounding box (Always 0)
234
+ #
235
+ def bottom
236
+ 0
237
+ end
238
+
239
+ # Relative top-left point of the bounding_box
240
+ #
241
+ def top_left
242
+ [left,top]
243
+ end
244
+
245
+ # Relative top-right point of the bounding box
246
+ #
247
+ def top_right
248
+ [right,top]
249
+ end
250
+
251
+ # Relative bottom-right point of the bounding box
252
+ #
253
+ def bottom_right
254
+ [right,bottom]
255
+ end
256
+
257
+ # Relative bottom-left point of the bounding box
258
+ #
259
+ def bottom_left
260
+ [left,bottom]
261
+ end
262
+
263
+ # Absolute left x-coordinate of the bounding box
264
+ #
265
+ def absolute_left
266
+ @x
267
+ end
268
+
269
+ # Absolute right x-coordinate of the bounding box
270
+ #
271
+ def absolute_right
272
+ @x + width
273
+ end
274
+
275
+ # Absolute top y-coordinate of the bounding box
276
+ #
277
+ def absolute_top
278
+ @y
279
+ end
280
+
281
+ # Absolute bottom y-coordinate of the bottom box
282
+ #
283
+ def absolute_bottom
284
+ @y - height
285
+ end
286
+
287
+ # Absolute top-left point of the bounding box
288
+ #
289
+ def absolute_top_left
290
+ [absolute_left, absolute_top]
291
+ end
292
+
293
+ # Absolute top-right point of the bounding box
294
+ #
295
+ def absolute_top_right
296
+ [absolute_right, absolute_top]
297
+ end
298
+
299
+ # Absolute bottom-left point of the bounding box
300
+ #
301
+ def absolute_bottom_left
302
+ [absolute_left, absolute_bottom]
303
+ end
304
+
305
+ # Absolute bottom-left point of the bounding box
306
+ #
307
+ def absolute_bottom_right
308
+ [absolute_right, absolute_bottom]
309
+ end
310
+
311
+ # Width of the bounding box
312
+ #
313
+ def width
314
+ @width
315
+ end
316
+
317
+ # Height of the bounding box. If the box is 'stretchy' (unspecified
318
+ # height attribute), height is calculated as the distance from the top of
319
+ # the box to the current drawing position.
320
+ #
321
+ def height
322
+ @height || absolute_top - @parent.y
323
+ end
324
+
325
+ # Returns +false+ when the box has a defined height, +true+ when the height
326
+ # is being calculated on the fly based on the current vertical position.
327
+ #
328
+ def stretchy?
329
+ !@height
330
+ end
331
+
332
+ end
333
+
334
+ class LazyBoundingBox < BoundingBox
335
+
336
+ # Defines the block to be executed by LazyBoundingBox#draw.
337
+ # Usually, this will be used via a higher level interface.
338
+ # See the documentation for Document#lazy_bounding_box, Document#header,
339
+ # and Document#footer
340
+ #
341
+ def action(&block)
342
+ @action = block
343
+ end
344
+
345
+ # Sets Document#bounds to use the LazyBoundingBox for its bounds,
346
+ # runs the block specified by LazyBoundingBox#action,
347
+ # and then restores the original bounds of the document.
348
+ #
349
+ def draw
350
+ @parent.mask(:y) do
351
+ parent_box = @parent.bounds
352
+ @parent.bounds = self
353
+ @parent.y = absolute_top
354
+ @action.call
355
+ @parent.bounds = parent_box
356
+ end
357
+ end
358
+
359
+ end
360
+
361
+ end
362
+ end