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