prawn-core 0.6.3 → 0.7.1
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/Rakefile +1 -1
- data/examples/general/context_sensitive_headers.rb +37 -0
- data/examples/general/float.rb +11 -0
- data/examples/general/repeaters.rb +43 -0
- data/examples/m17n/chinese_text_wrapping.rb +1 -3
- data/examples/text/font_calculations.rb +6 -6
- data/examples/text/text_box.rb +80 -17
- data/lib/prawn/core.rb +3 -1
- data/lib/prawn/document/bounding_box.rb +9 -0
- data/lib/prawn/document/column_box.rb +13 -2
- data/lib/prawn/document/internals.rb +21 -3
- data/lib/prawn/document/snapshot.rb +7 -2
- data/lib/prawn/document/span.rb +3 -3
- data/lib/prawn/document.rb +78 -19
- data/lib/prawn/font/afm.rb +10 -7
- data/lib/prawn/font/ttf.rb +6 -4
- data/lib/prawn/font.rb +34 -24
- data/lib/prawn/graphics/cap_style.rb +5 -2
- data/lib/prawn/graphics/color.rb +117 -57
- data/lib/prawn/graphics/dash.rb +4 -2
- data/lib/prawn/graphics/join_style.rb +6 -3
- data/lib/prawn/graphics/transparency.rb +65 -18
- data/lib/prawn/images/jpg.rb +1 -1
- data/lib/prawn/images/png.rb +1 -1
- data/lib/prawn/object_store.rb +30 -1
- data/lib/prawn/reference.rb +25 -3
- data/lib/prawn/repeater.rb +117 -0
- data/lib/prawn/stamp.rb +102 -40
- data/lib/prawn/text/box.rb +344 -0
- data/lib/prawn/text.rb +255 -0
- data/spec/document_spec.rb +125 -4
- data/spec/object_store_spec.rb +33 -0
- data/spec/repeater_spec.rb +79 -0
- data/spec/stamp_spec.rb +8 -0
- data/spec/text_box_spec.rb +282 -69
- data/spec/text_spec.rb +49 -29
- data/spec/transparency_spec.rb +14 -0
- data/vendor/pdf-inspector/lib/pdf/inspector/graphics.rb +2 -2
- metadata +158 -155
- data/examples/general/measurement_units.pdf +0 -4667
- data/lib/prawn/document/text/box.rb +0 -90
- data/lib/prawn/document/text/wrapping.rb +0 -62
- data/lib/prawn/document/text.rb +0 -184
data/lib/prawn/text.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# text.rb : Implements PDF text primitives
|
4
|
+
#
|
5
|
+
# Copyright May 2008, Gregory Brown. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
require "zlib"
|
9
|
+
require "prawn/text/box"
|
10
|
+
|
11
|
+
module Prawn
|
12
|
+
module Text
|
13
|
+
attr_reader :text_options
|
14
|
+
attr_reader :skip_encoding
|
15
|
+
|
16
|
+
ruby_18 { $KCODE="U" }
|
17
|
+
|
18
|
+
# Gets height of text in PDF points. See text() for valid options.
|
19
|
+
#
|
20
|
+
def height_of(string, options={})
|
21
|
+
box = Text::Box.new(string,
|
22
|
+
options.merge(:height => 100000000,
|
23
|
+
:document => self))
|
24
|
+
box.render(:dry_run => true)
|
25
|
+
height = box.height - box.descender
|
26
|
+
height += box.line_height + box.leading - box.ascender # if final_gap
|
27
|
+
height
|
28
|
+
end
|
29
|
+
|
30
|
+
# If you want text to flow onto a new page or between columns, this is the
|
31
|
+
# method to use. If, instead, if you want to place bounded text outside of
|
32
|
+
# the flow of a document (for captions, labels, charts, etc.), use Text::Box
|
33
|
+
# or its convenience method text_box.
|
34
|
+
#
|
35
|
+
# Draws text on the page. If a point is specified via the <tt>:at</tt>
|
36
|
+
# option the text will begin exactly at that point, and the string is
|
37
|
+
# assumed to be pre-formatted to properly fit the page.
|
38
|
+
#
|
39
|
+
# pdf.text "Hello World", :at => [100,100]
|
40
|
+
# pdf.text "Goodbye World", :at => [50,50], :size => 16
|
41
|
+
#
|
42
|
+
# When <tt>:at</tt> is not specified, Prawn attempts to wrap the text to
|
43
|
+
# fit within your current bounding box (or margin_box if no bounding box
|
44
|
+
# is being used ). Text will flow onto the next page when it reaches
|
45
|
+
# the bottom of the bounding box. Text wrap in Prawn does not re-flow
|
46
|
+
# linebreaks, so if you want fully automated text wrapping, be sure to
|
47
|
+
# remove newlines before attempting to draw your string.
|
48
|
+
#
|
49
|
+
# pdf.text "Will be wrapped when it hits the edge of your bounding box"
|
50
|
+
# pdf.text "This will be centered", :align => :center
|
51
|
+
# pdf.text "This will be right aligned", :align => :right
|
52
|
+
#
|
53
|
+
# If your font contains kerning pairs data that Prawn can parse, the
|
54
|
+
# text will be kerned by default. You can disable this feature by passing
|
55
|
+
# <tt>:kerning => false</tt>.
|
56
|
+
#
|
57
|
+
# === Text Positioning Details:
|
58
|
+
#
|
59
|
+
# When using the :at parameter, Prawn will position your text by the
|
60
|
+
# left-most edge of its baseline, and flow along a single line. (This
|
61
|
+
# means that :align will not work)
|
62
|
+
#
|
63
|
+
# Otherwise, the text is positioned at font.ascender below the baseline,
|
64
|
+
# making it easy to use this method within bounding boxes and spans.
|
65
|
+
#
|
66
|
+
# == Rotation
|
67
|
+
#
|
68
|
+
# Text can be rotated before it is placed on the canvas by specifying the
|
69
|
+
# <tt>:rotate</tt> option with a given angle. Rotation occurs counter-clockwise.
|
70
|
+
# Note that <tt>:rotate</tt> is only compatible when using the <tt>:at</tt> option
|
71
|
+
#
|
72
|
+
# == Encoding
|
73
|
+
#
|
74
|
+
# Note that strings passed to this function should be encoded as UTF-8.
|
75
|
+
# If you get unexpected characters appearing in your rendered document,
|
76
|
+
# check this.
|
77
|
+
#
|
78
|
+
# If the current font is a built-in one, although the string must be
|
79
|
+
# encoded as UTF-8, only characters that are available in WinAnsi
|
80
|
+
# are allowed.
|
81
|
+
#
|
82
|
+
# If an empty box is rendered to your PDF instead of the character you
|
83
|
+
# wanted it usually means the current font doesn't include that character.
|
84
|
+
#
|
85
|
+
# == Options (default values marked in [])
|
86
|
+
#
|
87
|
+
# <tt>:kerning</tt>:: <tt>boolean</tt>. Whether or not to use kerning (if it
|
88
|
+
# is available with the current font) [true]
|
89
|
+
# <tt>:size</tt>:: <tt>number</tt>. The font size to use. [current font
|
90
|
+
# size]
|
91
|
+
# <tt>:style</tt>:: The style to use. The requested style must be part of
|
92
|
+
# the current font familly. [current style]
|
93
|
+
#
|
94
|
+
# === Additional options available when <tt>:at</tt> option is provided
|
95
|
+
#
|
96
|
+
# <tt>:at</tt>:: <tt>[x, y]</tt>. The position at which to start the text
|
97
|
+
# <tt>:rotate</tt>:: <tt>number</tt>. The angle to which to rotate text
|
98
|
+
#
|
99
|
+
# === Additional options available when <tt>:at</tt> option is omitted
|
100
|
+
#
|
101
|
+
# <tt>:align</tt>:: <tt>:left</tt>, <tt>:center</tt>, or <tt>:right</tt>.
|
102
|
+
# Alignment within the bounding box [:left]
|
103
|
+
# <tt>:valign</tt>:: <tt>:top</tt>, <tt>:center</tt>, or <tt>:bottom</tt>.
|
104
|
+
# Vertical alignment within the bounding box [:top]
|
105
|
+
# <tt>:leading</tt>:: <tt>number</tt>. Additional space between lines [0]
|
106
|
+
# <tt>:final_gap</tt>:: <tt>boolean</tt>. If true, then the space between
|
107
|
+
# each line is included below the last line;
|
108
|
+
# otherwise, document.y is placed just below the
|
109
|
+
# descender of the last line printed [true]
|
110
|
+
# <tt>:wrap_block</tt>:: <tt>proc</tt>. A proc used for custom line
|
111
|
+
# wrapping. The proc must accept a single
|
112
|
+
# <tt>line</tt> of text and an <tt>options</tt> hash
|
113
|
+
# and return the string from that single line that
|
114
|
+
# can fit on the line under the conditions defined by
|
115
|
+
# <tt>options</tt>. If omitted, the default wrapping
|
116
|
+
# proc is used. The options hash passed into the
|
117
|
+
# wrap_block proc includes the following options:
|
118
|
+
# <tt>:width</tt>:: the width available for the
|
119
|
+
# current line of text
|
120
|
+
# <tt>:document</tt>:: the pdf object
|
121
|
+
# <tt>:kerning</tt>:: boolean
|
122
|
+
# <tt>:size</tt>:: the font size
|
123
|
+
#
|
124
|
+
# Raises <tt>ArgumentError</tt> if both <tt>:at</tt> and <tt>:align</tt>
|
125
|
+
# options included
|
126
|
+
#
|
127
|
+
# Raises <tt>ArgumentError</tt> if <tt>:rotate</tt> option included, but
|
128
|
+
# <tt>:at</tt> option omitted
|
129
|
+
#
|
130
|
+
def text(text, options={})
|
131
|
+
# we might modify the options. don't change the user's hash
|
132
|
+
options = options.dup
|
133
|
+
if options[:at]
|
134
|
+
inspect_options_for_text_at(options)
|
135
|
+
# we'll be messing with the strings encoding, don't change the user's
|
136
|
+
# original string
|
137
|
+
text = text.to_s.dup
|
138
|
+
options = @text_options.merge(options)
|
139
|
+
save_font do
|
140
|
+
process_text_options(options)
|
141
|
+
font.normalize_encoding!(text) unless @skip_encoding
|
142
|
+
font_size(options[:size]) { text_at(text, options) }
|
143
|
+
end
|
144
|
+
else
|
145
|
+
remaining_text = fill_text_box(text, options)
|
146
|
+
while remaining_text.length > 0
|
147
|
+
@bounding_box.move_past_bottom
|
148
|
+
previous_remaining_text = remaining_text
|
149
|
+
remaining_text = fill_text_box(remaining_text, options)
|
150
|
+
break if remaining_text == previous_remaining_text
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Low level text placement method. All font and size alterations
|
156
|
+
# should already be set
|
157
|
+
#
|
158
|
+
def text_at(text, options)
|
159
|
+
x,y = translate(options[:at])
|
160
|
+
add_text_content(text,x,y,options)
|
161
|
+
end
|
162
|
+
|
163
|
+
# These should be used as a base. Extensions may build on this list
|
164
|
+
VALID_TEXT_OPTIONS = [:kerning, :size, :style]
|
165
|
+
|
166
|
+
# Low level call to set the current font style and extract text options from
|
167
|
+
# an options hash. Should be called from within a save_font block
|
168
|
+
#
|
169
|
+
def process_text_options(options)
|
170
|
+
if options[:style]
|
171
|
+
raise "Bad font family" unless font.family
|
172
|
+
font(font.family, :style => options[:style])
|
173
|
+
end
|
174
|
+
|
175
|
+
# must compare against false to keep kerning on as default
|
176
|
+
unless options[:kerning] == false
|
177
|
+
options[:kerning] = font.has_kerning_data?
|
178
|
+
end
|
179
|
+
|
180
|
+
options[:size] ||= font_size
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def fill_text_box(text, options)
|
186
|
+
final_gap = inspect_options_for_text_box(options)
|
187
|
+
bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom :
|
188
|
+
@bounding_box.absolute_bottom
|
189
|
+
|
190
|
+
options[:height] = y - bottom
|
191
|
+
options[:width] = bounds.width
|
192
|
+
options[:at] = [@bounding_box.left_side - @bounding_box.absolute_left,
|
193
|
+
y - @bounding_box.absolute_bottom]
|
194
|
+
|
195
|
+
box = Text::Box.new(text, options)
|
196
|
+
remaining_text = box.render
|
197
|
+
|
198
|
+
self.y -= box.height - box.descender
|
199
|
+
self.y -= box.line_height + box.leading - box.ascender if final_gap
|
200
|
+
|
201
|
+
remaining_text
|
202
|
+
end
|
203
|
+
|
204
|
+
def inspect_options_for_text_at(options)
|
205
|
+
if options[:align]
|
206
|
+
raise ArgumentError, "The :align option does not work with :at"
|
207
|
+
end
|
208
|
+
valid_options = VALID_TEXT_OPTIONS.dup.concat([:at, :rotate])
|
209
|
+
Prawn.verify_options(valid_options, options)
|
210
|
+
end
|
211
|
+
|
212
|
+
def inspect_options_for_text_box(options)
|
213
|
+
if options[:rotate]
|
214
|
+
raise ArgumentError, "Rotated text may only be used with :at"
|
215
|
+
end
|
216
|
+
options.merge!(:document => self)
|
217
|
+
final_gap = options[:final_gap].nil? ? true : options[:final_gap]
|
218
|
+
options.delete(:final_gap)
|
219
|
+
final_gap
|
220
|
+
end
|
221
|
+
|
222
|
+
def move_text_position(dy)
|
223
|
+
bottom = @bounding_box.stretchy? ? @margin_box.absolute_bottom :
|
224
|
+
@bounding_box.absolute_bottom
|
225
|
+
|
226
|
+
@bounding_box.move_past_bottom if (y - dy) < bottom
|
227
|
+
|
228
|
+
self.y -= dy
|
229
|
+
end
|
230
|
+
|
231
|
+
def add_text_content(text, x, y, options)
|
232
|
+
chunks = font.encode_text(text,options)
|
233
|
+
|
234
|
+
add_content "\nBT"
|
235
|
+
|
236
|
+
if options[:rotate]
|
237
|
+
rad = options[:rotate].to_i * Math::PI / 180
|
238
|
+
arr = [ Math.cos(rad), Math.sin(rad), -Math.sin(rad), Math.cos(rad), x, y ]
|
239
|
+
add_content "%.3f %.3f %.3f %.3f %.3f %.3f Tm" % arr
|
240
|
+
else
|
241
|
+
add_content "#{x} #{y} Td"
|
242
|
+
end
|
243
|
+
|
244
|
+
chunks.each do |(subset, string)|
|
245
|
+
font.add_to_current_page(subset)
|
246
|
+
add_content "/#{font.identifier_for(subset)} #{font_size} Tf"
|
247
|
+
|
248
|
+
operation = options[:kerning] && string.is_a?(Array) ? "TJ" : "Tj"
|
249
|
+
add_content Prawn::PdfObject(string, true) << " " << operation
|
250
|
+
end
|
251
|
+
|
252
|
+
add_content "ET\n"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
data/spec/document_spec.rb
CHANGED
@@ -55,7 +55,6 @@ describe "when generating a document from a subclass" do
|
|
55
55
|
|
56
56
|
end
|
57
57
|
|
58
|
-
|
59
58
|
describe "When creating multi-page documents" do
|
60
59
|
|
61
60
|
before(:each) { create_pdf }
|
@@ -94,6 +93,84 @@ describe "When beginning each new page" do
|
|
94
93
|
@pdf.instance_variable_defined?(:@background).should == true
|
95
94
|
@pdf.instance_variable_get(:@background).should == @filename
|
96
95
|
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "The page_number method" do
|
104
|
+
it "should be 1 for a new document" do
|
105
|
+
pdf = Prawn::Document.new
|
106
|
+
pdf.page_number.should == 1
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should be 0 for documents with no pages" do
|
110
|
+
pdf = Prawn::Document.new(:skip_page_creation => true)
|
111
|
+
pdf.page_number.should == 0
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should be changed by go_to_page" do
|
115
|
+
pdf = Prawn::Document.new
|
116
|
+
10.times { pdf.start_new_page }
|
117
|
+
pdf.go_to_page 3
|
118
|
+
pdf.page_number.should == 3
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "on_page_create callback" do
|
124
|
+
before do
|
125
|
+
create_pdf
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should be invoked with document" do
|
129
|
+
called_with = nil
|
130
|
+
|
131
|
+
@pdf.on_page_create { |*args| called_with = args }
|
132
|
+
|
133
|
+
@pdf.start_new_page
|
134
|
+
|
135
|
+
called_with.should == [@pdf]
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should be invoked for each new page" do
|
139
|
+
trigger = mock()
|
140
|
+
trigger.expects(:fire).times(5)
|
141
|
+
|
142
|
+
@pdf.on_page_create { trigger.fire }
|
143
|
+
|
144
|
+
5.times { @pdf.start_new_page }
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should be replaceable" do
|
148
|
+
trigger1 = mock()
|
149
|
+
trigger1.expects(:fire).times(1)
|
150
|
+
|
151
|
+
trigger2 = mock()
|
152
|
+
trigger2.expects(:fire).times(1)
|
153
|
+
|
154
|
+
@pdf.on_page_create { trigger1.fire }
|
155
|
+
|
156
|
+
@pdf.start_new_page
|
157
|
+
|
158
|
+
@pdf.on_page_create { trigger2.fire }
|
159
|
+
|
160
|
+
@pdf.start_new_page
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should be clearable by calling on_page_create without a block" do
|
164
|
+
trigger = mock()
|
165
|
+
trigger.expects(:fire).times(1)
|
166
|
+
|
167
|
+
@pdf.on_page_create { trigger.fire }
|
168
|
+
|
169
|
+
@pdf.start_new_page
|
170
|
+
|
171
|
+
@pdf.on_page_create
|
172
|
+
|
173
|
+
@pdf.start_new_page
|
97
174
|
end
|
98
175
|
|
99
176
|
end
|
@@ -148,6 +225,21 @@ describe "When reopening pages" do
|
|
148
225
|
lambda{ PDF::Inspector::Page.analyze(@pdf.render) }.
|
149
226
|
should.not.raise(PDF::Reader::MalformedPDFError)
|
150
227
|
end
|
228
|
+
|
229
|
+
it "should insert pages after the current page when calling start_new_page" do
|
230
|
+
pdf = Prawn::Document.new
|
231
|
+
3.times { |i| pdf.text "Old page #{i+1}"; pdf.start_new_page }
|
232
|
+
pdf.go_to_page 1
|
233
|
+
pdf.start_new_page
|
234
|
+
pdf.text "New page 2"
|
235
|
+
|
236
|
+
pdf.page_number.should == 2
|
237
|
+
|
238
|
+
pages = PDF::Inspector::Page.analyze(pdf.render).pages
|
239
|
+
pages.size.should == 5
|
240
|
+
pages[1][:strings].should == ["New page 2"]
|
241
|
+
pages[2][:strings].should == ["Old page 2"]
|
242
|
+
end
|
151
243
|
end
|
152
244
|
|
153
245
|
describe "When setting page size" do
|
@@ -255,17 +347,46 @@ describe "The render() feature" do
|
|
255
347
|
pdf = Prawn::Document.new
|
256
348
|
|
257
349
|
seq = sequence("callback_order")
|
258
|
-
|
350
|
+
|
259
351
|
# Verify the order: finalize -> fire callbacks -> render body
|
260
352
|
pdf.expects(:finalize_all_page_contents).in_sequence(seq)
|
261
353
|
trigger = mock()
|
262
354
|
trigger.expects(:fire).in_sequence(seq)
|
355
|
+
|
356
|
+
# Store away the render_body method to be called below
|
357
|
+
render_body = pdf.method(:render_body)
|
263
358
|
pdf.expects(:render_body).in_sequence(seq)
|
264
|
-
|
359
|
+
|
265
360
|
pdf.before_render{ trigger.fire }
|
266
|
-
|
361
|
+
|
362
|
+
# Render the body to set up object offsets
|
363
|
+
render_body.call(StringIO.new)
|
267
364
|
pdf.render
|
268
365
|
end
|
366
|
+
|
367
|
+
end
|
368
|
+
|
369
|
+
describe "The :optimize_objects option" do
|
370
|
+
before(:all) do
|
371
|
+
@wasteful_doc = lambda do
|
372
|
+
transaction { start_new_page; text "Hidden text"; rollback }
|
373
|
+
text "Hello world"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should result in fewer objects when enabled" do
|
378
|
+
wasteful_pdf = Prawn::Document.new(&@wasteful_doc)
|
379
|
+
frugal_pdf = Prawn::Document.new(:optimize_objects => true,
|
380
|
+
&@wasteful_doc)
|
381
|
+
frugal_pdf.render.size.should.be < wasteful_pdf.render.size
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should default to :false" do
|
385
|
+
default_pdf = Prawn::Document.new(&@wasteful_doc)
|
386
|
+
wasteful_pdf = Prawn::Document.new(:optimize_objects => false,
|
387
|
+
&@wasteful_doc)
|
388
|
+
default_pdf.render.size.should == wasteful_pdf.render.size
|
389
|
+
end
|
269
390
|
end
|
270
391
|
|
271
392
|
describe "PDF file versions" do
|
data/spec/object_store_spec.rb
CHANGED
@@ -40,3 +40,36 @@ describe "Prawn::ObjectStore" do
|
|
40
40
|
@store.map{|ref| ref.identifier}[-3..-1].should == [10, 11, 12]
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
describe "Prawn::ObjectStore#compact" do
|
45
|
+
it "should do nothing to an ObjectStore with all live refs" do
|
46
|
+
store = Prawn::ObjectStore.new
|
47
|
+
store.info.data[:Blah] = store.ref(:some => "structure")
|
48
|
+
old_size = store.size
|
49
|
+
store.compact
|
50
|
+
|
51
|
+
store.size.should == old_size
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should remove dead objects, renumbering live objects from 1" do
|
55
|
+
store = Prawn::ObjectStore.new
|
56
|
+
store.ref(:some => "structure")
|
57
|
+
old_size = store.size
|
58
|
+
store.compact
|
59
|
+
|
60
|
+
store.size.should.be < old_size
|
61
|
+
store.map{ |o| o.identifier }.should == (1..store.size).to_a
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should detect and remove dead objects that were once live" do
|
65
|
+
store = Prawn::ObjectStore.new
|
66
|
+
store.info.data[:Blah] = store.ref(:some => "structure")
|
67
|
+
store.info.data[:Blah] = :overwritten
|
68
|
+
old_size = store.size
|
69
|
+
store.compact
|
70
|
+
|
71
|
+
store.size.should.be < old_size
|
72
|
+
store.map{ |o| o.identifier }.should == (1..store.size).to_a
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), "spec_helper")
|
2
|
+
|
3
|
+
describe "Repeaters" do
|
4
|
+
|
5
|
+
it "creates a stamp and increments Prawn::Repeater.count on initialize" do
|
6
|
+
orig_count = Prawn::Repeater.count
|
7
|
+
|
8
|
+
doc = sample_document
|
9
|
+
doc.expects(:create_stamp).with("prawn_repeater(#{orig_count})")
|
10
|
+
|
11
|
+
r = repeater(doc, :all) { :do_nothing }
|
12
|
+
|
13
|
+
assert_equal orig_count + 1, Prawn::Repeater.count
|
14
|
+
end
|
15
|
+
|
16
|
+
it "must provide an :all filter" do
|
17
|
+
doc = sample_document
|
18
|
+
r = repeater(doc, :all) { :do_nothing }
|
19
|
+
|
20
|
+
assert (1..doc.page_count).all? { |i| r.match?(i) }
|
21
|
+
end
|
22
|
+
|
23
|
+
it "must provide an :odd filter" do
|
24
|
+
doc = sample_document
|
25
|
+
r = repeater(doc, :odd) { :do_nothing }
|
26
|
+
|
27
|
+
odd, even = (1..doc.page_count).partition { |e| e % 2 == 1 }
|
28
|
+
|
29
|
+
assert odd.all? { |i| r.match?(i) }
|
30
|
+
assert ! even.any? { |i| r.match?(i) }
|
31
|
+
end
|
32
|
+
|
33
|
+
it "must be able to filter by an array of page numbers" do
|
34
|
+
doc = sample_document
|
35
|
+
r = repeater(doc, [1,2,7]) { :do_nothing }
|
36
|
+
|
37
|
+
assert_equal [1,2,7], (1..10).select { |i| r.match?(i) }
|
38
|
+
end
|
39
|
+
|
40
|
+
it "must be able to filter by a range of page numbers" do
|
41
|
+
doc = sample_document
|
42
|
+
r = repeater(doc, 2..4) { :do_nothing }
|
43
|
+
|
44
|
+
assert_equal [2,3,4], (1..10).select { |i| r.match?(i) }
|
45
|
+
end
|
46
|
+
|
47
|
+
it "must be able to filter by an arbitrary proc" do
|
48
|
+
doc = sample_document
|
49
|
+
r = repeater(doc, lambda { |x| x == 1 or x % 3 == 0 })
|
50
|
+
|
51
|
+
assert_equal [1,3,6,9], (1..10).select { |i| r.match?(i) }
|
52
|
+
end
|
53
|
+
|
54
|
+
it "must try to run a stamp if the page number matches" do
|
55
|
+
doc = sample_document
|
56
|
+
doc.expects(:stamp)
|
57
|
+
|
58
|
+
repeater(doc, :odd).run(3)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "must not try to run a stamp if the page number matches" do
|
62
|
+
doc = sample_document
|
63
|
+
|
64
|
+
doc.expects(:stamp).never
|
65
|
+
repeater(doc, :odd).run(2)
|
66
|
+
end
|
67
|
+
|
68
|
+
def sample_document
|
69
|
+
doc = Prawn::Document.new(:skip_page_creation => true)
|
70
|
+
10.times { |e| doc.start_new_page }
|
71
|
+
|
72
|
+
doc
|
73
|
+
end
|
74
|
+
|
75
|
+
def repeater(*args, &b)
|
76
|
+
Prawn::Repeater.new(*args,&b)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
data/spec/stamp_spec.rb
CHANGED
@@ -9,6 +9,14 @@ describe "create_stamp before any page is added" do
|
|
9
9
|
end
|
10
10
|
}.should.not.raise(Prawn::Errors::NotOnPage)
|
11
11
|
end
|
12
|
+
it "should work with setting color" do
|
13
|
+
@pdf = Prawn::Document.new(:skip_page_creation => true)
|
14
|
+
lambda {
|
15
|
+
@pdf.create_stamp("my_stamp") do
|
16
|
+
@pdf.fill_color = 'ff0000'
|
17
|
+
end
|
18
|
+
}.should.not.raise(Prawn::Errors::NotOnPage)
|
19
|
+
end
|
12
20
|
end
|
13
21
|
|
14
22
|
describe "Document with a stamp" do
|