prawn 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|