prawn 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/README +16 -2
  2. data/Rakefile +3 -3
  3. data/data/images/arrow.png +0 -0
  4. data/data/images/arrow2.png +0 -0
  5. data/data/images/barcode_issue.png +0 -0
  6. data/data/images/ruport_type0.png +0 -0
  7. data/examples/cell.rb +14 -3
  8. data/examples/chinese_text_wrapping.rb +17 -0
  9. data/examples/family_based_styling.rb +21 -0
  10. data/examples/fancy_table.rb +4 -4
  11. data/examples/flowing_text_with_header_and_footer.rb +72 -0
  12. data/examples/font_size.rb +2 -2
  13. data/examples/lazy_bounding_boxes.rb +19 -0
  14. data/examples/table.rb +13 -11
  15. data/examples/text_flow.rb +1 -1
  16. data/lib/prawn.rb +44 -15
  17. data/lib/prawn/compatibility.rb +20 -7
  18. data/lib/prawn/document.rb +72 -122
  19. data/lib/prawn/document/bounding_box.rb +124 -24
  20. data/lib/prawn/document/internals.rb +107 -0
  21. data/lib/prawn/document/table.rb +99 -70
  22. data/lib/prawn/document/text.rb +92 -314
  23. data/lib/prawn/errors.rb +13 -2
  24. data/lib/prawn/font.rb +312 -1
  25. data/lib/prawn/font/cmap.rb +1 -1
  26. data/lib/prawn/font/metrics.rb +52 -49
  27. data/lib/prawn/font/wrapping.rb +14 -12
  28. data/lib/prawn/graphics.rb +23 -74
  29. data/lib/prawn/graphics/cell.rb +30 -25
  30. data/lib/prawn/graphics/color.rb +132 -0
  31. data/lib/prawn/images.rb +37 -16
  32. data/lib/prawn/images/png.rb +29 -24
  33. data/lib/prawn/pdf_object.rb +3 -1
  34. data/spec/bounding_box_spec.rb +12 -3
  35. data/spec/document_spec.rb +40 -72
  36. data/spec/font_spec.rb +97 -0
  37. data/spec/graphics_spec.rb +46 -99
  38. data/spec/images_spec.rb +4 -21
  39. data/spec/pdf_object_spec.rb +8 -8
  40. data/spec/png_spec.rb +47 -12
  41. data/spec/spec_helper.rb +5 -24
  42. data/spec/table_spec.rb +53 -59
  43. data/spec/text_spec.rb +28 -93
  44. data/vendor/pdf-inspector/README +18 -0
  45. data/vendor/pdf-inspector/lib/pdf/inspector.rb +25 -0
  46. data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +80 -0
  47. data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +16 -0
  48. data/vendor/pdf-inspector/lib/pdf/inspector/text.rb +31 -0
  49. data/vendor/pdf-inspector/lib/pdf/inspector/xobject.rb +19 -0
  50. metadata +63 -38
  51. data/examples/on_page_start.rb +0 -17
  52. data/examples/table_bench.rb +0 -92
  53. data/spec/box_calculation_spec.rb +0 -17
@@ -11,10 +11,12 @@ require "prawn/document/page_geometry"
11
11
  require "prawn/document/bounding_box"
12
12
  require "prawn/document/text"
13
13
  require "prawn/document/table"
14
+ require "prawn/document/internals"
14
15
 
15
16
  module Prawn
16
17
  class Document
17
-
18
+
19
+ include Prawn::Document::Internals
18
20
  include Prawn::Graphics
19
21
  include Prawn::Images
20
22
  include Text
@@ -22,29 +24,33 @@ module Prawn
22
24
 
23
25
  attr_accessor :y, :margin_box
24
26
  attr_reader :margins, :page_size, :page_layout
25
-
26
-
27
+
27
28
  # Creates and renders a PDF document.
28
29
  #
29
- # The block argument is necessary only when you need to make
30
- # use of a closure.
31
- #
32
- # # Using implicit block form and rendering to a file
33
- # Prawn::Document.generate "foo.pdf" do
30
+ # When using the implicit block form, Prawn will evaluate the block
31
+ # within an instance of Prawn::Document, simplifying your syntax.
32
+ # However, please note that you will not be able to reference variables
33
+ # from the enclosing scope within this block.
34
+ #
35
+ # # Using implicit block form and rendering to a file
36
+ # Prawn::Document.generate "foo.pdf" do
34
37
  # font "Times-Roman"
35
38
  # text "Hello World", :at => [200,720], :size => 32
36
- # end
37
- #
38
- # # Using explicit block form and rendering to a file
39
- # content = "Hello World"
40
- # Prawn::Document.generate "foo.pdf" do |pdf|
39
+ # end
40
+ #
41
+ # If you need to access your local and instance variables, use the explicit
42
+ # block form shown below. In this case, Prawn yields an instance of
43
+ # PDF::Document and the block is an ordinary closure:
44
+ #
45
+ # # Using explicit block form and rendering to a file
46
+ # content = "Hello World"
47
+ # Prawn::Document.generate "foo.pdf" do |pdf|
41
48
  # pdf.font "Times-Roman"
42
49
  # pdf.text content, :at => [200,720], :size => 32
43
- # end
50
+ # end
44
51
  #
45
52
  def self.generate(filename,options={},&block)
46
- pdf = Prawn::Document.new(options)
47
- block.arity < 1 ? pdf.instance_eval(&block) : yield(pdf)
53
+ pdf = Prawn::Document.new(options,&block)
48
54
  pdf.render_file(filename)
49
55
  end
50
56
 
@@ -61,27 +67,29 @@ module Prawn
61
67
  # <tt>:skip_page_creation</tt>:: Creates a document without starting the first page [false]
62
68
  # <tt>:compress</tt>:: Compresses content streams before rendering them [false]
63
69
  #
70
+ # Usage:
64
71
  #
65
72
  # # New document, US Letter paper, portrait orientation
66
73
  # pdf = Prawn::Document.new
67
74
  #
68
75
  # # New document, A4 paper, landscaped
69
76
  # pdf = Prawn::Document.new(:page_size => "A4", :page_layout => :landscape)
70
- #
71
- # # New document, draws a line at the start of each new page
72
- # pdf = Prawn::Document.new(:on_page_start =>
73
- # lambda { |doc| doc.line [0,100], [300,100] } )
74
77
  #
75
- def initialize(options={})
78
+ def initialize(options={},&block)
79
+ Prawn.verify_options [:page_size, :page_layout, :on_page_start,
80
+ :on_page_stop, :left_margin, :right_margin, :top_margin,
81
+ :bottom_margin, :skip_page_creation, :compress ], options
82
+
76
83
  @objects = []
77
84
  @info = ref(:Creator => "Prawn", :Producer => "Prawn")
78
85
  @pages = ref(:Type => :Pages, :Count => 0, :Kids => [])
79
- @root = ref(:Type => :Catalog, :Pages => @pages)
80
- @page_start_proc = options[:on_page_start]
81
- @page_stop_proc = options[:on_page_stop]
82
- @page_size = options[:page_size] || "LETTER"
83
- @page_layout = options[:page_layout] || :portrait
84
- @compress = options[:compress] || false
86
+ @root = ref(:Type => :Catalog, :Pages => @pages)
87
+ @page_size = options[:page_size] || "LETTER"
88
+ @page_layout = options[:page_layout] || :portrait
89
+ @compress = options[:compress] || false
90
+ @skip_encoding = options[:skip_encoding]
91
+
92
+ text_options.update(options[:text_options] || {})
85
93
 
86
94
  @margins = { :left => options[:left_margin] || 36,
87
95
  :right => options[:right_margin] || 36,
@@ -92,12 +100,14 @@ module Prawn
92
100
 
93
101
  @bounding_box = @margin_box
94
102
 
95
- start_new_page unless options[:skip_page_creation]
103
+ start_new_page unless options[:skip_page_creation]
104
+
105
+ if block
106
+ block.arity < 1 ? instance_eval(&block) : block[self]
107
+ end
96
108
  end
97
109
 
98
- # Creates and advances to a new page in the document.
99
- # Runs the <tt>on_page_start</tt> lambda if one was provided at
100
- # document creation time (See Document.new).
110
+ # Creates and advances to a new page in the document.
101
111
  #
102
112
  # Page size, margins, and layout can also be set when generating a
103
113
  # new page. These values will become the new defaults for page creation
@@ -116,22 +126,14 @@ module Prawn
116
126
  end
117
127
 
118
128
  finish_page_content if @page_content
119
- generate_margin_box
120
- @page_content = ref(:Length => 0)
121
-
122
- @current_page = ref(:Type => :Page,
123
- :Parent => @pages,
124
- :MediaBox => page_dimensions,
125
- :Contents => @page_content)
126
- set_current_font
127
- update_colors
129
+ build_new_page_content
130
+
128
131
  @pages.data[:Kids] << @current_page
129
132
  @pages.data[:Count] += 1
130
133
 
131
134
  add_content "q"
132
135
 
133
- @y = @margin_box.absolute_top
134
- @page_start_proc[self] if @page_start_proc
136
+ @y = @bounding_box.absolute_top
135
137
  end
136
138
 
137
139
  # Returns the number of pages in the document
@@ -176,6 +178,13 @@ module Prawn
176
178
  #
177
179
  def bounds
178
180
  @bounding_box
181
+ end
182
+
183
+ # Sets Document#bounds to the BoundingBox provided. If you don't know
184
+ # why you'd need to do this, chances are, you can ignore this feature
185
+ #
186
+ def bounds=(bounding_box)
187
+ @bounding_box = bounding_box
179
188
  end
180
189
 
181
190
  # Moves up the document by n points
@@ -231,7 +240,6 @@ module Prawn
231
240
  move_down(y)
232
241
  end
233
242
 
234
-
235
243
  def mask(*fields) # :nodoc:
236
244
  # Stores the current state of the named attributes, executes the block, and
237
245
  # then restores the original values after the block has executed.
@@ -241,14 +249,30 @@ module Prawn
241
249
  yield
242
250
  fields.each { |f| send("#{f}=", stored[f]) }
243
251
  end
244
-
252
+
253
+ # Returns true if content streams will be compressed before rendering,
254
+ # false otherwise
255
+ #
245
256
  def compression_enabled?
246
- @compress
247
- end
248
-
257
+ !!@compress
258
+ end
249
259
 
250
260
  private
251
261
 
262
+ # See Prawn::Document::Internals for low-level PDF functions
263
+
264
+ def build_new_page_content
265
+ generate_margin_box
266
+ @page_content = ref(:Length => 0)
267
+
268
+ @current_page = ref(:Type => :Page,
269
+ :Parent => @pages,
270
+ :MediaBox => page_dimensions,
271
+ :Contents => @page_content)
272
+ font.add_to_current_page if @font_name
273
+ update_colors
274
+ end
275
+
252
276
  def generate_margin_box
253
277
  old_margin_box = @margin_box
254
278
  @margin_box = BoundingBox.new(
@@ -259,84 +283,10 @@ module Prawn
259
283
  )
260
284
 
261
285
  # update bounding box if not flowing from the previous page
262
- # TODO: This may have a bug where the old margin is restored
286
+ # FIXME: This may have a bug where the old margin is restored
263
287
  # when the bounding box exits.
264
288
  @bounding_box = @margin_box if old_margin_box == @bounding_box
265
289
  end
266
-
267
- def ref(data)
268
- @objects.push(Prawn::Reference.new(@objects.size + 1, data)).last
269
- end
270
-
271
- def add_content(str)
272
- @page_content << str << "\n"
273
- end
274
-
275
- # Add a new type to the current pages ProcSet
276
- def proc_set(*types)
277
- @current_page.data[:ProcSet] ||= ref([])
278
- @current_page.data[:ProcSet].data |= types
279
- end
280
-
281
- def page_resources
282
- @current_page.data[:Resources] ||= {}
283
- end
284
-
285
- def page_fonts
286
- page_resources[:Font] ||= {}
287
- end
288
-
289
- def page_xobjects
290
- page_resources[:XObject] ||= {}
291
- end
292
-
293
- def finish_page_content
294
- @page_stop_proc[self] if @page_stop_proc
295
- add_content "Q"
296
- @page_content.compress_stream if compression_enabled?
297
- @page_content.data[:Length] = @page_content.stream.size
298
- end
299
290
 
300
- # Write out the PDF Header, as per spec 3.4.1
301
- def render_header(output)
302
- # pdf version
303
- output << "%PDF-1.3\n"
304
-
305
- # 4 binary chars, as recommended by the spec
306
- output << "\xFF\xFF\xFF\xFF\n"
307
- end
308
-
309
- # Write out the PDF Body, as per spec 3.4.2
310
- def render_body(output)
311
- @objects.each do |ref|
312
- ref.offset = output.size
313
- output << ref.object
314
- end
315
- end
316
-
317
- # Write out the PDF Cross Reference Table, as per spec 3.4.3
318
- def render_xref(output)
319
- @xref_offset = output.size
320
- output << "xref\n"
321
- output << "0 #{@objects.size + 1}\n"
322
- output << "0000000000 65535 f \n"
323
- @objects.each do |ref|
324
- output.printf("%010d", ref.offset)
325
- output << " 00000 n \n"
326
- end
327
- end
328
-
329
- # Write out the PDF Body, as per spec 3.4.4
330
- def render_trailer(output)
331
- trailer_hash = {:Size => @objects.size + 1,
332
- :Root => @root,
333
- :Info => @info}
334
-
335
- output << "trailer\n"
336
- output << Prawn::PdfObject(trailer_hash) << "\n"
337
- output << "startxref\n"
338
- output << @xref_offset << "\n"
339
- output << "%%EOF"
340
- end
341
291
  end
342
292
  end
@@ -11,16 +11,15 @@ module Prawn
11
11
  # When flowing text, the usage of a bounding box is simple. Text will
12
12
  # begin at the point specified, flowing the width of the bounding box.
13
13
  # After the block exits, the text drawing position will be moved to
14
- # the bottom of the bounding box (y - height). Currently, Prawn allows
15
- # text to overflow the bottom border of the bounding box, so it is up to
16
- # the user to ensure the text provided will fit within the height of the
17
- # bounding box.
18
- #
19
- # pdf.bounding_box([100,500], :width => 100, :height => 300) do
20
- # pdf.text "This text will flow in a very narrow box starting" +
21
- # "from [100,500]. The pointer will then be moved to [100,200]" +
22
- # "and return to the margin_box"
23
- # end
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
24
23
  #
25
24
  # When translating coordinates, the idea is to allow the user to draw
26
25
  # relative to the origin, and then translate their drawing to a specified
@@ -84,16 +83,43 @@ module Prawn
84
83
  # you remain within the printable area of your document.
85
84
  #
86
85
  def bounding_box(*args, &block)
87
- init_bounding_box(block) do |parent_box|
88
- # Offset to relative positions
89
- top_left = args[0]
90
- top_left[0] += parent_box.absolute_left
91
- top_left[1] += parent_box.absolute_bottom
92
-
86
+ init_bounding_box(block) do |_|
87
+ translate!(args[0])
93
88
  @bounding_box = BoundingBox.new(self, *args)
94
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
95
121
  end
96
-
122
+
97
123
  # A shortcut to produce a bounding box which is mapped to the document's
98
124
  # absolute coordinates, regardless of how things are nested or margin sizes.
99
125
  #
@@ -108,7 +134,37 @@ module Prawn
108
134
  :height => page_dimensions[3]
109
135
  )
110
136
  end
111
- end
137
+ end
138
+
139
+ # A header is a LazyBoundingBox drawn relative to the margins that can be
140
+ # repeated on every page of the document.
141
+ #
142
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
143
+ # width and height
144
+ #
145
+ # header margin_box.top_left do
146
+ # text "Here's My Fancy Header", :size => 25, :align => :center
147
+ # stroke_horizontal_rule
148
+ # end
149
+ #
150
+ def header(top_left,options={},&block)
151
+ @header = repeating_page_element(top_left,options,&block)
152
+ end
153
+
154
+ # A footer is a LazyBoundingBox drawn relative to the margins that can be
155
+ # repeated on every page of the document.
156
+ #
157
+ # Unless <tt>:width</tt> or <tt>:height</tt> are specified, the margin_box
158
+ # width and height
159
+ #
160
+ # footer [margin_box.left, margin_box.bottom + 25] do
161
+ # stroke_horizontal_rule
162
+ # text "And here's a sexy footer", :size => 16
163
+ # end
164
+ #
165
+ def footer(top_left,options={},&block)
166
+ @footer = repeating_page_element(top_left,options,&block)
167
+ end
112
168
 
113
169
  private
114
170
 
@@ -122,16 +178,24 @@ module Prawn
122
178
  self.y = @bounding_box.absolute_bottom
123
179
 
124
180
  @bounding_box = parent_box
125
- end
126
-
181
+ end
182
+
183
+ def repeating_page_element(top_left,options={},&block)
184
+ r = LazyBoundingBox.new(self, translate(top_left),
185
+ :width => options[:width] || margin_box.width,
186
+ :height => options[:height] || margin_box.height )
187
+ r.action(&block)
188
+ return r
189
+ end
190
+
127
191
  class BoundingBox
128
192
 
129
- def initialize(parent, point, options={}) #:nodoc:
193
+ def initialize(parent, point, options={}) #:nodoc:
130
194
  @parent = parent
131
195
  @x, @y = point
132
196
  @width, @height = options[:width], options[:height]
133
- end
134
-
197
+ end
198
+
135
199
  # The translated origin (x,y-height) which describes the location
136
200
  # of the bottom left corner of the bounding box
137
201
  #
@@ -245,9 +309,45 @@ module Prawn
245
309
  # height attribute), height is calculated as the distance from the top of
246
310
  # the box to the current drawing position.
247
311
  #
248
- def height
312
+ def height
249
313
  @height || absolute_top - @parent.y
314
+ end
315
+
316
+ # Returns +false+ when the box has a defined height, +true+ when the height
317
+ # is being calculated on the fly based on the current vertical position.
318
+ #
319
+ def stretchy?
320
+ !@height
250
321
  end
322
+
323
+ end
324
+
325
+ class LazyBoundingBox < BoundingBox
326
+
327
+ # Defines the block to be executed by LazyBoundingBox#draw.
328
+ # Usually, this will be used via a higher level interface.
329
+ # See the documentation for Document#lazy_bounding_box, Document#header,
330
+ # and Document#footer
331
+ #
332
+ def action(&block)
333
+ @action = block
334
+ end
335
+
336
+ # Sets Document#bounds to use the LazyBoundingBox for its bounds,
337
+ # runs the block specified by LazyBoundingBox#action,
338
+ # and then restores the original bounds of the document.
339
+ #
340
+ def draw
341
+ @parent.mask(:y) do
342
+ parent_box = @parent.bounds
343
+ @parent.bounds = self
344
+ @parent.y = absolute_top
345
+ @action.call
346
+ @parent.bounds = parent_box
347
+ end
348
+ end
349
+
251
350
  end
351
+
252
352
  end
253
353
  end