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.
- data/COPYING +340 -0
- data/LICENSE +56 -0
- data/README +47 -0
- data/Rakefile +76 -0
- data/data/fonts/Activa.ttf +0 -0
- data/data/fonts/Chalkboard.ttf +0 -0
- data/data/fonts/Courier-Bold.afm +342 -0
- data/data/fonts/Courier-BoldOblique.afm +342 -0
- data/data/fonts/Courier-Oblique.afm +342 -0
- data/data/fonts/Courier.afm +342 -0
- data/data/fonts/DejaVuSans.ttf +0 -0
- data/data/fonts/Dustismo_Roman.ttf +0 -0
- data/data/fonts/Helvetica-Bold.afm +2827 -0
- data/data/fonts/Helvetica-BoldOblique.afm +2827 -0
- data/data/fonts/Helvetica-Oblique.afm +3051 -0
- data/data/fonts/Helvetica.afm +3051 -0
- data/data/fonts/MustRead.html +19 -0
- data/data/fonts/Symbol.afm +213 -0
- data/data/fonts/Times-Bold.afm +2588 -0
- data/data/fonts/Times-BoldItalic.afm +2384 -0
- data/data/fonts/Times-Italic.afm +2667 -0
- data/data/fonts/Times-Roman.afm +2419 -0
- data/data/fonts/ZapfDingbats.afm +225 -0
- data/data/fonts/comicsans.ttf +0 -0
- data/data/fonts/gkai00mp.ttf +0 -0
- 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/dice.alpha +0 -0
- data/data/images/dice.dat +0 -0
- data/data/images/dice.png +0 -0
- data/data/images/page_white_text.alpha +0 -0
- data/data/images/page_white_text.dat +0 -0
- data/data/images/page_white_text.png +0 -0
- data/data/images/pigs.jpg +0 -0
- data/data/images/rails.dat +0 -0
- data/data/images/rails.png +0 -0
- data/data/images/ruport.png +0 -0
- data/data/images/ruport_data.dat +0 -0
- data/data/images/ruport_transparent.png +0 -0
- data/data/images/ruport_type0.png +0 -0
- data/data/images/stef.jpg +0 -0
- data/data/images/web-links.dat +1 -0
- data/data/images/web-links.png +0 -0
- data/data/shift_jis_text.txt +1 -0
- data/examples/addressbook.csv +6 -0
- data/examples/alignment.rb +16 -0
- data/examples/bounding_boxes.rb +30 -0
- data/examples/canvas.rb +12 -0
- data/examples/cell.rb +38 -0
- data/examples/chinese_text_wrapping.rb +17 -0
- data/examples/currency.csv +1834 -0
- data/examples/curves.rb +10 -0
- data/examples/family_based_styling.rb +21 -0
- data/examples/fancy_table.rb +61 -0
- data/examples/flowing_text_with_header_and_footer.rb +72 -0
- data/examples/font_size.rb +27 -0
- data/examples/hexagon.rb +14 -0
- data/examples/image.rb +23 -0
- data/examples/image2.rb +13 -0
- data/examples/image_flow.rb +34 -0
- data/examples/kerning.rb +27 -0
- data/examples/lazy_bounding_boxes.rb +19 -0
- data/examples/line.rb +31 -0
- data/examples/multi_page_layout.rb +14 -0
- data/examples/page_geometry.rb +28 -0
- data/examples/png_types.rb +23 -0
- data/examples/polygons.rb +16 -0
- data/examples/position_by_baseline.rb +26 -0
- data/examples/ruport_formatter.rb +50 -0
- data/examples/ruport_helpers.rb +18 -0
- data/examples/russian_boxes.rb +34 -0
- data/examples/simple_text.rb +15 -0
- data/examples/simple_text_ttf.rb +16 -0
- data/examples/sjis.rb +21 -0
- data/examples/span.rb +27 -0
- data/examples/table.rb +47 -0
- data/examples/table_header_color.rb +16 -0
- data/examples/text_flow.rb +65 -0
- data/examples/top_and_bottom_cells.rb +40 -0
- data/examples/utf8.rb +12 -0
- data/lib/prawn.rb +67 -0
- data/lib/prawn/compatibility.rb +46 -0
- data/lib/prawn/document.rb +309 -0
- data/lib/prawn/document/bounding_box.rb +362 -0
- data/lib/prawn/document/internals.rb +113 -0
- data/lib/prawn/document/page_geometry.rb +79 -0
- data/lib/prawn/document/span.rb +47 -0
- data/lib/prawn/document/table.rb +350 -0
- data/lib/prawn/document/text.rb +196 -0
- data/lib/prawn/errors.rb +48 -0
- data/lib/prawn/font.rb +356 -0
- data/lib/prawn/font/cmap.rb +59 -0
- data/lib/prawn/font/metrics.rb +378 -0
- data/lib/prawn/font/wrapping.rb +47 -0
- data/lib/prawn/graphics.rb +252 -0
- data/lib/prawn/graphics/cell.rb +264 -0
- data/lib/prawn/graphics/color.rb +132 -0
- data/lib/prawn/images.rb +336 -0
- data/lib/prawn/images/jpg.rb +45 -0
- data/lib/prawn/images/png.rb +199 -0
- data/lib/prawn/pdf_object.rb +73 -0
- data/lib/prawn/reference.rb +56 -0
- data/spec/bounding_box_spec.rb +141 -0
- data/spec/document_spec.rb +181 -0
- data/spec/font_spec.rb +141 -0
- data/spec/graphics_spec.rb +209 -0
- data/spec/images_spec.rb +68 -0
- data/spec/jpg_spec.rb +25 -0
- data/spec/metrics_spec.rb +62 -0
- data/spec/pdf_object_spec.rb +112 -0
- data/spec/png_spec.rb +196 -0
- data/spec/reference_spec.rb +42 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/table_spec.rb +179 -0
- data/spec/text_spec.rb +135 -0
- 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
|