prawn 0.1.2 → 0.2.0

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