pdf-wrapper 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +5 -0
- data/Rakefile +5 -1
- data/examples/table.rb +1 -0
- data/examples/table_images.rb +41 -0
- data/examples/table_simple.rb +20 -0
- data/lib/pdf/wrapper.rb +2 -0
- data/lib/pdf/wrapper/table.rb +248 -307
- data/lib/pdf/wrapper/text.rb +12 -12
- data/lib/pdf/wrapper/text_cell.rb +53 -0
- data/lib/pdf/wrapper/text_image_cell.rb +68 -0
- data/specs/graphics_spec.rb +8 -8
- data/specs/tables_spec.rb +234 -1
- data/specs/text_spec.rb +2 -2
- data/specs/wrapper_spec.rb +4 -4
- metadata +10 -7
data/lib/pdf/wrapper/text.rb
CHANGED
@@ -200,6 +200,18 @@ module PDF
|
|
200
200
|
return width / Pango::SCALE
|
201
201
|
end
|
202
202
|
|
203
|
+
def default_text_options
|
204
|
+
{ :font => @default_font,
|
205
|
+
:font_size => @default_font_size,
|
206
|
+
:alignment => :left,
|
207
|
+
:wrap => :wordchar,
|
208
|
+
:justify => false,
|
209
|
+
:spacing => 0,
|
210
|
+
:color => nil,
|
211
|
+
:markup => nil
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
203
215
|
private
|
204
216
|
|
205
217
|
# takes a string and a range of options and creates a pango layout for us. Pango
|
@@ -299,18 +311,6 @@ module PDF
|
|
299
311
|
return layout
|
300
312
|
end
|
301
313
|
|
302
|
-
def default_text_options
|
303
|
-
{ :font => @default_font,
|
304
|
-
:font_size => @default_font_size,
|
305
|
-
:alignment => :left,
|
306
|
-
:wrap => :wordchar,
|
307
|
-
:justify => false,
|
308
|
-
:spacing => 0,
|
309
|
-
:color => nil,
|
310
|
-
:markup => nil
|
311
|
-
}
|
312
|
-
end
|
313
|
-
|
314
314
|
# renders a pango layout onto our main context
|
315
315
|
# based on a function of the same name found in the text2.rb sample file
|
316
316
|
# distributed with rcairo - it's still black magic to me and has a few edge
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module PDF
|
4
|
+
class Wrapper
|
5
|
+
class TextCell
|
6
|
+
|
7
|
+
attr_reader :data, :min_width, :natural_width, :max_width, :wrapper
|
8
|
+
attr_accessor :width, :height
|
9
|
+
attr_writer :options
|
10
|
+
|
11
|
+
def initialize(str)
|
12
|
+
@data = str.to_s
|
13
|
+
@options = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def draw(wrapper, x, y)
|
17
|
+
@wrapper = wrapper
|
18
|
+
|
19
|
+
wrapper.cell(self.data, x, y, self.width, self.height, self.options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def calculate_width_range(wrapper)
|
23
|
+
@wrapper = wrapper
|
24
|
+
|
25
|
+
padding = options[:padding] || 3
|
26
|
+
if options[:markup] == :pango
|
27
|
+
str = self.data.dup.gsub(/<.+?>/,"").gsub("&","&").gsub("<","<").gsub(">",">")
|
28
|
+
else
|
29
|
+
str = self.data.dup
|
30
|
+
end
|
31
|
+
@min_width = wrapper.text_width(str.gsub(/\b|\B/,"\n"), text_options) + (padding * 4)
|
32
|
+
@natural_width = wrapper.text_width(str, text_options) + (padding * 4)
|
33
|
+
end
|
34
|
+
|
35
|
+
def calculate_height(wrapper)
|
36
|
+
raise "Cannot calculate height until cell width is set" if self.width.nil?
|
37
|
+
|
38
|
+
@wrapper = wrapper
|
39
|
+
|
40
|
+
padding = options[:padding] || 3
|
41
|
+
@height = wrapper.text_height(self.data, self.width - (padding * 2), text_options) + (padding * 2)
|
42
|
+
end
|
43
|
+
|
44
|
+
def options
|
45
|
+
@options ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def text_options
|
49
|
+
self.options.only(wrapper.default_text_options.keys)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module PDF
|
4
|
+
class Wrapper
|
5
|
+
class TextImageCell
|
6
|
+
|
7
|
+
attr_reader :text, :min_width, :natural_width, :max_width, :wrapper
|
8
|
+
attr_accessor :width, :height
|
9
|
+
attr_writer :options
|
10
|
+
|
11
|
+
def initialize(str, filename, width, height)
|
12
|
+
@text = str.to_s
|
13
|
+
@filename = filename
|
14
|
+
@min_width = width
|
15
|
+
@natural_width = width
|
16
|
+
@max_width = width
|
17
|
+
@height = height
|
18
|
+
@options = {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def draw(wrapper, x, y)
|
22
|
+
@wrapper = wrapper
|
23
|
+
|
24
|
+
wrapper.cell(self.text, x, y, self.width, self.height, self.options)
|
25
|
+
wrapper.image(@filename, image_options(x,y))
|
26
|
+
end
|
27
|
+
|
28
|
+
def calculate_width_range(wrapper)
|
29
|
+
# nothing required, width range set in constructor
|
30
|
+
end
|
31
|
+
|
32
|
+
def calculate_height(wrapper)
|
33
|
+
# nothing required, height set in constructor
|
34
|
+
end
|
35
|
+
|
36
|
+
def options
|
37
|
+
@options ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def image_offset
|
43
|
+
@image_offset ||= text_height + 4
|
44
|
+
end
|
45
|
+
|
46
|
+
def image_options(x, y)
|
47
|
+
{
|
48
|
+
:left => x,
|
49
|
+
:top => y + image_offset,
|
50
|
+
:width => self.width,
|
51
|
+
:height => self.height - image_offset,
|
52
|
+
:proportional => true,
|
53
|
+
:center => true
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def text_height
|
58
|
+
padding = options[:padding] || 3
|
59
|
+
wrapper.text_height(self.text, self.width - (padding * 2), text_options) + (padding * 2)
|
60
|
+
end
|
61
|
+
|
62
|
+
def text_options
|
63
|
+
self.options.only(wrapper.default_text_options.keys)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/specs/graphics_spec.rb
CHANGED
@@ -18,8 +18,8 @@ context "The PDF::Wrapper class" do
|
|
18
18
|
# the begin_new_subpath command specifies the start of the line, append line specifies the end
|
19
19
|
receiver.count(:begin_new_subpath).should eql(1)
|
20
20
|
receiver.count(:append_line).should eql(1)
|
21
|
-
receiver.first_occurance_of(:begin_new_subpath)[:args].should eql([x0
|
22
|
-
receiver.first_occurance_of(:append_line)[:args].should eql([x1
|
21
|
+
receiver.first_occurance_of(:begin_new_subpath)[:args].should eql([x0, 741.89])
|
22
|
+
receiver.first_occurance_of(:append_line)[:args].should eql([x1, 641.89])
|
23
23
|
end
|
24
24
|
|
25
25
|
specify "should be able to draw a single line onto the canvas with a width of 5" do
|
@@ -36,9 +36,9 @@ context "The PDF::Wrapper class" do
|
|
36
36
|
receiver.count(:set_line_width).should eql(1)
|
37
37
|
receiver.count(:begin_new_subpath).should eql(1)
|
38
38
|
receiver.count(:append_line).should eql(1)
|
39
|
-
receiver.first_occurance_of(:set_line_width)[:args].should eql([width
|
40
|
-
receiver.first_occurance_of(:begin_new_subpath)[:args].should eql([x0
|
41
|
-
receiver.first_occurance_of(:append_line)[:args].should eql([x1
|
39
|
+
receiver.first_occurance_of(:set_line_width)[:args].should eql([width])
|
40
|
+
receiver.first_occurance_of(:begin_new_subpath)[:args].should eql([x0, 741.89])
|
41
|
+
receiver.first_occurance_of(:append_line)[:args].should eql([x1, 641.89])
|
42
42
|
end
|
43
43
|
|
44
44
|
specify "should be able to draw a cubic bezier spline onto the canvas"
|
@@ -57,7 +57,7 @@ context "The PDF::Wrapper class" do
|
|
57
57
|
callbacks.size.should eql(2)
|
58
58
|
# don't care about the first rectangel, it just goes around the outside of the page
|
59
59
|
callbacks.shift
|
60
|
-
callbacks.shift[:args].should eql([100
|
60
|
+
callbacks.shift[:args].should eql([100, 741.89, 200, -200])
|
61
61
|
end
|
62
62
|
|
63
63
|
specify "should be able to draw an empty rectangle onto the canvas with a line width of 5" do
|
@@ -72,14 +72,14 @@ context "The PDF::Wrapper class" do
|
|
72
72
|
|
73
73
|
# ensure the line width was set correctly
|
74
74
|
receiver.count(:set_line_width).should eql(1)
|
75
|
-
receiver.first_occurance_of(:set_line_width)[:args].should eql([width
|
75
|
+
receiver.first_occurance_of(:set_line_width)[:args].should eql([width])
|
76
76
|
|
77
77
|
# the begin_new_subpath command specifies the start of the line, append line specifies the end
|
78
78
|
callbacks = receiver.all(:append_rectangle)
|
79
79
|
callbacks.size.should eql(2)
|
80
80
|
# don't care about the first rectangel, it just goes around the outside of the page
|
81
81
|
callbacks.shift
|
82
|
-
callbacks.shift[:args].should eql([100
|
82
|
+
callbacks.shift[:args].should eql([100, 741.89, 200, -200])
|
83
83
|
end
|
84
84
|
|
85
85
|
specify "should be able to draw a filled rectangle onto the canvas"
|
data/specs/tables_spec.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require File.dirname(__FILE__) + '/spec_helper'
|
4
4
|
|
5
|
-
context
|
5
|
+
context PDF::Wrapper do
|
6
6
|
|
7
7
|
before(:each) { create_pdf }
|
8
8
|
|
@@ -20,6 +20,41 @@ context "The PDF::Wrapper class" do
|
|
20
20
|
receiver.content.first.include?("data4").should be_true
|
21
21
|
end
|
22
22
|
|
23
|
+
specify "should be able to draw a table on the canvas using an array of TextCells" do
|
24
|
+
data = [
|
25
|
+
[ PDF::Wrapper::TextCell.new("data1"), PDF::Wrapper::TextCell.new("data2")],
|
26
|
+
[ PDF::Wrapper::TextCell.new("data3"), PDF::Wrapper::TextCell.new("data4")]
|
27
|
+
]
|
28
|
+
@pdf.table(data)
|
29
|
+
@pdf.finish
|
30
|
+
|
31
|
+
receiver = PageTextReceiver.new
|
32
|
+
reader = PDF::Reader.string(@output.string, receiver)
|
33
|
+
|
34
|
+
receiver.content.first.include?("data1").should be_true
|
35
|
+
receiver.content.first.include?("data2").should be_true
|
36
|
+
receiver.content.first.include?("data3").should be_true
|
37
|
+
receiver.content.first.include?("data4").should be_true
|
38
|
+
end
|
39
|
+
|
40
|
+
specify "should be able to draw a table on the canvas using an array of TextImageCells" do
|
41
|
+
filename = File.dirname(__FILE__) + "/data/orc.svg"
|
42
|
+
data = [
|
43
|
+
[ "data1", PDF::Wrapper::TextImageCell.new("data2", filename, 100, 100)],
|
44
|
+
[ "data3", PDF::Wrapper::TextImageCell.new("data4", filename, 100, 100)]
|
45
|
+
]
|
46
|
+
@pdf.table(data)
|
47
|
+
@pdf.finish
|
48
|
+
|
49
|
+
receiver = PageTextReceiver.new
|
50
|
+
reader = PDF::Reader.string(@output.string, receiver)
|
51
|
+
|
52
|
+
receiver.content.first.include?("data1").should be_true
|
53
|
+
receiver.content.first.include?("data2").should be_true
|
54
|
+
receiver.content.first.include?("data3").should be_true
|
55
|
+
receiver.content.first.include?("data4").should be_true
|
56
|
+
end
|
57
|
+
|
23
58
|
specify "should be able to draw a table on the canvas using a PDF::Wrapper::Table object" do
|
24
59
|
table = PDF::Wrapper::Table.new do |t|
|
25
60
|
t.data = [%w{data1 data2}, %w{data3 data4}]
|
@@ -110,3 +145,201 @@ context "The PDF::Wrapper class" do
|
|
110
145
|
end
|
111
146
|
|
112
147
|
end
|
148
|
+
|
149
|
+
context PDF::Wrapper, "data= method" do
|
150
|
+
|
151
|
+
specify "should raise an exception if given rows of uneven size" do
|
152
|
+
data = [%w{head1 head2},%w{data1}]
|
153
|
+
table = PDF::Wrapper::Table.new
|
154
|
+
lambda { table.data = data }.should raise_error(ArgumentError)
|
155
|
+
end
|
156
|
+
|
157
|
+
specify "should convert all non cell objects to TextCells" do
|
158
|
+
data = [%w{head1 head2},%w{data1 data2}]
|
159
|
+
table = PDF::Wrapper::Table.new
|
160
|
+
table.data = data
|
161
|
+
table.each_cell do |cell|
|
162
|
+
cell.should be_a_kind_of(PDF::Wrapper::TextCell)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
specify "should leave existing TextCells unchanged" do
|
167
|
+
manual_cell_one = PDF::Wrapper::TextCell.new("data1")
|
168
|
+
manual_cell_two = PDF::Wrapper::TextCell.new("data2")
|
169
|
+
data = [[manual_cell_one, manual_cell_two]]
|
170
|
+
|
171
|
+
table = PDF::Wrapper::Table.new
|
172
|
+
table.data = data
|
173
|
+
|
174
|
+
cells = []
|
175
|
+
table.each_cell do |cell|
|
176
|
+
cells << cell
|
177
|
+
end
|
178
|
+
(cells[0] === manual_cell_one).should be_true
|
179
|
+
(cells[1] === manual_cell_two).should be_true
|
180
|
+
end
|
181
|
+
|
182
|
+
specify "should leave existing TextImageCells unchanged" do
|
183
|
+
manual_cell_one = PDF::Wrapper::TextImageCell.new("data1", "image.png", 100, 100)
|
184
|
+
manual_cell_two = PDF::Wrapper::TextImageCell.new("data2", "image.png", 100, 100)
|
185
|
+
data = [[manual_cell_one, manual_cell_two]]
|
186
|
+
|
187
|
+
table = PDF::Wrapper::Table.new
|
188
|
+
table.data = data
|
189
|
+
|
190
|
+
cells = []
|
191
|
+
table.each_cell do |cell|
|
192
|
+
cells << cell
|
193
|
+
end
|
194
|
+
(cells[0] === manual_cell_one).should be_true
|
195
|
+
(cells[1] === manual_cell_two).should be_true
|
196
|
+
end
|
197
|
+
|
198
|
+
specify "should set the default table options on all cells" do
|
199
|
+
data = [%w{head1 head2},%w{data1 data2}]
|
200
|
+
table = PDF::Wrapper::Table.new(:markup => :pango)
|
201
|
+
|
202
|
+
table.data = data
|
203
|
+
|
204
|
+
table.each_cell do |cell|
|
205
|
+
cell.options.should eql(:markup => :pango)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context PDF::Wrapper, "headers method" do
|
211
|
+
|
212
|
+
specify "should raise an exception if given cell count does not match existing data" do
|
213
|
+
data = [%w{data1 data2},%w{data1 data2}]
|
214
|
+
headers = %w{head1}
|
215
|
+
|
216
|
+
table = PDF::Wrapper::Table.new
|
217
|
+
table.data = data
|
218
|
+
|
219
|
+
lambda { table.headers(headers) }.should raise_error(ArgumentError)
|
220
|
+
end
|
221
|
+
|
222
|
+
specify "should wrap non-cell objects in a TextCell" do
|
223
|
+
headers = [["head1","head2"]]
|
224
|
+
|
225
|
+
table = PDF::Wrapper::Table.new
|
226
|
+
table.headers(headers)
|
227
|
+
|
228
|
+
set_headers = table.instance_variable_get("@headers")
|
229
|
+
set_headers.each do |cell|
|
230
|
+
cell.should be_a_kind_of(PDF::Wrapper::TextCell)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
specify "should leave TextCell objects untouched" do
|
235
|
+
manual_cell_one = PDF::Wrapper::TextCell.new("data1")
|
236
|
+
manual_cell_two = PDF::Wrapper::TextCell.new("data2")
|
237
|
+
headers = [manual_cell_one, manual_cell_two]
|
238
|
+
|
239
|
+
table = PDF::Wrapper::Table.new
|
240
|
+
table.headers(headers)
|
241
|
+
|
242
|
+
set_headers = table.instance_variable_get("@headers")
|
243
|
+
(set_headers[0] === manual_cell_one).should be_true
|
244
|
+
(set_headers[1] === manual_cell_two).should be_true
|
245
|
+
end
|
246
|
+
|
247
|
+
specify "should leave TextImageCell objects untouched" do
|
248
|
+
manual_cell_one = PDF::Wrapper::TextImageCell.new("data1", "image.png", 100, 100)
|
249
|
+
manual_cell_two = PDF::Wrapper::TextImageCell.new("data2", "image.png", 100, 100)
|
250
|
+
headers = [manual_cell_one, manual_cell_two]
|
251
|
+
|
252
|
+
table = PDF::Wrapper::Table.new
|
253
|
+
table.headers(headers)
|
254
|
+
|
255
|
+
set_headers = table.instance_variable_get("@headers")
|
256
|
+
(set_headers[0] === manual_cell_one).should be_true
|
257
|
+
(set_headers[1] === manual_cell_two).should be_true
|
258
|
+
end
|
259
|
+
|
260
|
+
specify "should set options on all cells" do
|
261
|
+
headers = ["head1","head2"]
|
262
|
+
|
263
|
+
table = PDF::Wrapper::Table.new
|
264
|
+
table.headers(headers, :markup => :pango)
|
265
|
+
|
266
|
+
set_headers = table.instance_variable_get("@headers")
|
267
|
+
set_headers.each do |cell|
|
268
|
+
cell.options.should eql(:markup => :pango)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
specify "should set default table options on all cells" do
|
273
|
+
headers = ["head1","head2"]
|
274
|
+
|
275
|
+
table = PDF::Wrapper::Table.new(:markup => :pango)
|
276
|
+
table.headers(headers)
|
277
|
+
|
278
|
+
set_headers = table.instance_variable_get("@headers")
|
279
|
+
set_headers.each do |cell|
|
280
|
+
cell.options.should eql(:markup => :pango)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context PDF::Wrapper, "cell method" do
|
286
|
+
|
287
|
+
specify "should return the appropriate cell" do
|
288
|
+
data = [%w{data1 data2},%w{data3 data4}]
|
289
|
+
headers = %w{head1}
|
290
|
+
|
291
|
+
table = PDF::Wrapper::Table.new
|
292
|
+
table.data = data
|
293
|
+
|
294
|
+
table.cell(0,0).should be_a_kind_of(PDF::Wrapper::TextCell)
|
295
|
+
table.cell(0,0).data.should eql("data1")
|
296
|
+
|
297
|
+
table.cell(1,1).should be_a_kind_of(PDF::Wrapper::TextCell)
|
298
|
+
table.cell(1,1).data.should eql("data4")
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
context PDF::Wrapper, "cell_options method" do
|
303
|
+
|
304
|
+
specify "should set options on the appropriate cell" do
|
305
|
+
data = [%w{data1 data2},%w{data3 data4}]
|
306
|
+
|
307
|
+
table = PDF::Wrapper::Table.new
|
308
|
+
table.data = data
|
309
|
+
table.cell_options(0,0, :markup => :pango)
|
310
|
+
|
311
|
+
table.cell(0,0).options.should eql(:markup => :pango)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
context PDF::Wrapper, "col_options method" do
|
316
|
+
|
317
|
+
specify "should set options on all cells in the appropriate column" do
|
318
|
+
data = [%w{data1 data2},%w{data3 data4}]
|
319
|
+
|
320
|
+
table = PDF::Wrapper::Table.new
|
321
|
+
table.data = data
|
322
|
+
table.col_options(0, :markup => :pango)
|
323
|
+
|
324
|
+
table.cell(0,0).options.should eql(:markup => :pango)
|
325
|
+
table.cell(0,1).options.should eql(:markup => :pango)
|
326
|
+
table.cell(1,0).options.should eql({})
|
327
|
+
table.cell(1,1).options.should eql({})
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
context PDF::Wrapper, "row_options method" do
|
332
|
+
|
333
|
+
specify "should set options on all cells in the appropriate row" do
|
334
|
+
data = [%w{data1 data2},%w{data3 data4}]
|
335
|
+
|
336
|
+
table = PDF::Wrapper::Table.new
|
337
|
+
table.data = data
|
338
|
+
table.row_options(0, :markup => :pango)
|
339
|
+
|
340
|
+
table.cell(0,0).options.should eql(:markup => :pango)
|
341
|
+
table.cell(1,0).options.should eql(:markup => :pango)
|
342
|
+
table.cell(0,1).options.should eql({})
|
343
|
+
table.cell(1,1).options.should eql({})
|
344
|
+
end
|
345
|
+
end
|