fullcirclegroup-prawn 0.2.99.3

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 (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