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.
- data/README +16 -2
- data/Rakefile +3 -3
- data/data/images/arrow.png +0 -0
- data/data/images/arrow2.png +0 -0
- data/data/images/barcode_issue.png +0 -0
- data/data/images/ruport_type0.png +0 -0
- data/examples/cell.rb +14 -3
- data/examples/chinese_text_wrapping.rb +17 -0
- data/examples/family_based_styling.rb +21 -0
- data/examples/fancy_table.rb +4 -4
- data/examples/flowing_text_with_header_and_footer.rb +72 -0
- data/examples/font_size.rb +2 -2
- data/examples/lazy_bounding_boxes.rb +19 -0
- data/examples/table.rb +13 -11
- data/examples/text_flow.rb +1 -1
- data/lib/prawn.rb +44 -15
- data/lib/prawn/compatibility.rb +20 -7
- data/lib/prawn/document.rb +72 -122
- data/lib/prawn/document/bounding_box.rb +124 -24
- data/lib/prawn/document/internals.rb +107 -0
- data/lib/prawn/document/table.rb +99 -70
- data/lib/prawn/document/text.rb +92 -314
- data/lib/prawn/errors.rb +13 -2
- data/lib/prawn/font.rb +312 -1
- data/lib/prawn/font/cmap.rb +1 -1
- data/lib/prawn/font/metrics.rb +52 -49
- data/lib/prawn/font/wrapping.rb +14 -12
- data/lib/prawn/graphics.rb +23 -74
- data/lib/prawn/graphics/cell.rb +30 -25
- data/lib/prawn/graphics/color.rb +132 -0
- data/lib/prawn/images.rb +37 -16
- data/lib/prawn/images/png.rb +29 -24
- data/lib/prawn/pdf_object.rb +3 -1
- data/spec/bounding_box_spec.rb +12 -3
- data/spec/document_spec.rb +40 -72
- data/spec/font_spec.rb +97 -0
- data/spec/graphics_spec.rb +46 -99
- data/spec/images_spec.rb +4 -21
- data/spec/pdf_object_spec.rb +8 -8
- data/spec/png_spec.rb +47 -12
- data/spec/spec_helper.rb +5 -24
- data/spec/table_spec.rb +53 -59
- data/spec/text_spec.rb +28 -93
- data/vendor/pdf-inspector/README +18 -0
- data/vendor/pdf-inspector/lib/pdf/inspector.rb +25 -0
- data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +80 -0
- data/vendor/pdf-inspector/lib/pdf/inspector/page.rb +16 -0
- data/vendor/pdf-inspector/lib/pdf/inspector/text.rb +31 -0
- data/vendor/pdf-inspector/lib/pdf/inspector/xobject.rb +19 -0
- metadata +63 -38
- data/examples/on_page_start.rb +0 -17
- data/examples/table_bench.rb +0 -92
- data/spec/box_calculation_spec.rb +0 -17
data/lib/prawn/document.rb
CHANGED
@@ -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
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
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
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
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
|
-
#
|
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
|
-
@
|
81
|
-
@
|
82
|
-
@
|
83
|
-
@
|
84
|
-
|
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
|
-
|
120
|
-
|
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 = @
|
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
|
-
|
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
|
-
#
|
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).
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
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 |
|
88
|
-
|
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
|