podoff 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/spec/core_spec.rb CHANGED
@@ -12,11 +12,58 @@ describe Podoff do
12
12
 
13
13
  describe '.load' do
14
14
 
15
+ it 'loads a PDF document' do
16
+
17
+ d = Podoff.load('pdfs/t0.pdf')
18
+
19
+ expect(d.class).to eq(Podoff::Document)
20
+ expect(d.xref).to eq(414)
21
+ expect(d.objs.keys).to eq([ '1 0', '2 0', '3 0', '4 0', '5 0', '6 0' ])
22
+
23
+ #pp d.objs.values.collect(&:to_a)
24
+
25
+ expect(d.objs['1 0'].to_a).to eq(
26
+ [ '1 0', 9, 54, { type: '/Catalog' } ])
27
+ expect(d.objs['3 0'].to_a).to eq(
28
+ [ '3 0', 111, 213, { type: '/Page', contents: '6 0 R' } ])
29
+
30
+ expect(d.objs.values.first.document).not_to be(nil)
31
+
32
+ expect(d.obj_counters.keys).to eq(
33
+ [ '1 0', '2 0', '3 0', '4 0', '5 0', '6 0' ])
34
+ expect(d.obj_counters.values).to eq(
35
+ [ 1, 1, 1, 1, 1, 1 ])
36
+
37
+ expect(d.root).to eq('1 0')
38
+ end
39
+
15
40
  it 'loads a PDF document' do
16
41
 
17
42
  d = Podoff.load('pdfs/udocument0.pdf')
18
43
 
19
44
  expect(d.class).to eq(Podoff::Document)
45
+ expect(d.xref).to eq(3138351)
46
+ expect(d.objs.size).to eq(273)
47
+ expect(d.objs.keys).to include('1 0')
48
+ expect(d.objs.keys).to include('273 0')
49
+
50
+ expect(d.root).to eq('65 0')
51
+ end
52
+
53
+ it 'loads a PDF document with incremental updates' do
54
+
55
+ d = Podoff.load('pdfs/t1.pdf')
56
+
57
+ expect(d.class).to eq(Podoff::Document)
58
+ expect(d.xref).to eq(698)
59
+ expect(d.objs.keys).to eq([ '1 0', '2 0', '3 0', '4 0', '5 0', '6 0' ])
60
+
61
+ expect(d.obj_counters.keys).to eq(
62
+ [ '1 0', '2 0', '3 0', '4 0', '5 0', '6 0' ])
63
+ expect(d.obj_counters.values).to eq(
64
+ [ 1, 1, 1, 1, 1, 2 ])
65
+
66
+ expect(d.root).to eq('1 0')
20
67
  end
21
68
 
22
69
  it 'rejects items that are not PDF documents' do
@@ -39,9 +39,10 @@ describe Podoff::Document do
39
39
  it 'returns a page given an index (starts at 1)' do
40
40
 
41
41
  p = @d.page(1)
42
-
43
42
  expect(p.class).to eq(Podoff::Obj)
44
- expect(p.type).to eq('Page')
43
+ expect(p.type).to eq('/Page')
44
+ expect(p.attributes[:pagenum]).to eq('1')
45
+ expect(p.page_number).to eq(1)
45
46
  end
46
47
 
47
48
  it 'returns nil if the page doesn\'t exist' do
@@ -49,14 +50,30 @@ describe Podoff::Document do
49
50
  expect(@d.page(0)).to eq(nil)
50
51
  expect(@d.page(9)).to eq(nil)
51
52
  end
52
- end
53
53
 
54
- describe '#fonts' do
54
+ it 'returns the page, even for a doc without pdftk_PageNum' do
55
+
56
+ d = Podoff::Document.load('pdfs/t2.pdf')
55
57
 
56
- it 'returns the font obj' do
58
+ expect(d.page(1).ref).to eq('3 0')
59
+ expect(d.page(1).page_number).to eq(nil)
57
60
 
58
- expect(@d.fonts.size).to eq(35)
59
- expect(@d.fonts.first.class).to eq(Podoff::Obj)
61
+ expect(d.page(0)).to eq(nil)
62
+ expect(d.page(2)).to eq(nil)
63
+ end
64
+
65
+ it 'returns pages from the last when the index is negative' do
66
+
67
+ expect(@d.page(-1).ref).to eq('33 0')
68
+ expect(@d.page(-1).page_number).to eq(3)
69
+ end
70
+
71
+ it 'returns pages from the last when the index is negative (no PageNum)' do
72
+
73
+ d = Podoff::Document.load('pdfs/t2.pdf')
74
+
75
+ expect(d.page(-1).ref).to eq('3 0')
76
+ expect(d.page(-1).page_number).to eq(nil)
60
77
  end
61
78
  end
62
79
 
@@ -72,6 +89,29 @@ describe Podoff::Document do
72
89
  expect(lines.first).to match(/^%PDF-1.7$/)
73
90
  expect(lines.last).to match(/^%%EOF$/)
74
91
  end
92
+
93
+ it 'writes open streams as well' do
94
+
95
+ d = Podoff.load('pdfs/t0.pdf')
96
+
97
+ pa = d.re_add(d.page(1))
98
+ st = d.add_stream
99
+ st.bt(10, 20, 'hello open stream')
100
+ pa.insert_contents(st)
101
+
102
+ s = d.write(:string)
103
+
104
+ expect(
105
+ d.write(:string).index(%{
106
+ 7 0 obj
107
+ << /Length 37 >>
108
+ stream
109
+ BT 10 20 Td (hello open stream) Tj ET
110
+ endstream
111
+ endobj
112
+ }.strip)
113
+ ).to eq(722)
114
+ end
75
115
  end
76
116
 
77
117
  describe '#dup' do
@@ -80,15 +120,158 @@ describe Podoff::Document do
80
120
 
81
121
  d = @d.dup
82
122
 
83
- expect(d.class
84
- ).to eq(Podoff::Document)
85
- expect(d.objs.hash
86
- ).not_to eq(@d.objs.hash)
87
- expect(d.objs.values.first.hash
88
- ).not_to eq(@d.objs.values.first.hash)
123
+ expect(d.class).to eq(Podoff::Document)
124
+ expect(d.hash).not_to eq(@d.hash)
125
+
126
+ expect(d.objs.hash).not_to eq(@d.objs.hash)
127
+
128
+ expect(d.objs.values.first.hash).not_to eq(@d.objs.values.first.hash)
129
+ expect(d.objs.values.first.class).to eq(Podoff::Obj)
130
+ expect(d.objs.values.first.document.class).to eq(Podoff::Document)
131
+
132
+ expect(d.objs.values.first.document).to equal(d)
133
+ expect(@d.objs.values.first.document).to equal(@d)
134
+
135
+ expect(d.root).to eq('65 0')
136
+ end
137
+ end
138
+
139
+ context 'additions' do
140
+
141
+ before :each do
142
+
143
+ @d = Podoff.load('pdfs/t0.pdf')
144
+ end
145
+
146
+ describe '#add_base_font' do
147
+
148
+ it 'adds a new /Font obj to the document' do
149
+
150
+ fo = @d.add_base_font('Helvetica')
151
+
152
+ expect(@d.additions.size).to eq(1)
153
+ expect(@d.objs.keys).to eq((1..7).map { |i| "#{i} 0" })
154
+ expect(@d.additions.keys).to eq([ '7 0' ])
155
+
156
+ expect(fo.document).to eq(@d)
157
+ expect(fo.ref).to eq('7 0')
158
+
159
+ expect(fo.source).to eq(
160
+ '7 0 obj ' +
161
+ '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj')
162
+
163
+ s = @d.write(:string)
164
+ d = Podoff.parse(s)
165
+
166
+ expect(d.xref).to eq(682)
167
+ end
168
+
169
+ it 'doesn\'t mind a slash in front of the font name' do
170
+
171
+ fo = @d.add_base_font('/Helvetica')
172
+
173
+ expect(@d.additions.size).to eq(1)
174
+ expect(@d.objs.keys).to eq((1..7).map { |i| "#{i} 0" })
175
+ expect(@d.additions.keys).to eq([ '7 0' ])
176
+
177
+ expect(fo.document).to eq(@d)
178
+ expect(fo.ref).to eq('7 0')
179
+
180
+ expect(fo.source).to eq(
181
+ '7 0 obj ' +
182
+ '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj')
183
+ end
184
+ end
185
+
186
+ describe '#add_stream' do
187
+
188
+ it 'adds a new obj with a stream to the document' do
189
+
190
+ st = @d.add_stream('BT 70 80 Td /Helvetica 35 Tf (Hello!) Tj ET')
191
+
192
+ expect(@d.additions.size).to eq(1)
193
+ expect(@d.objs.keys).to eq((1..7).map { |i| "#{i} 0" })
194
+ expect(@d.additions.keys).to eq([ '7 0' ])
195
+
196
+ expect(st.document).to eq(@d)
197
+ expect(st.ref).to eq('7 0')
198
+
199
+ expect(st.source).to eq(%{
200
+ 7 0 obj
201
+ << /Length 43 >>
202
+ stream
203
+ BT 70 80 Td /Helvetica 35 Tf (Hello!) Tj ET
204
+ endstream
205
+ endobj
206
+ }.strip)
207
+
208
+ d = Podoff.parse(@d.write(:string))
209
+
210
+ expect(d.xref).to eq(705)
211
+ end
212
+
213
+ it 'accepts a block' do
214
+
215
+ st =
216
+ @d.add_stream {
217
+ tf '/Helvetica', 35
218
+ bt 10, 20, 'thirty here'
219
+ bt 40, 50, 'sixty there'
220
+ }
221
+
222
+ expect(st.obj.document).to eq(@d)
223
+ expect(st.obj.ref).to eq('7 0')
224
+
225
+ expect(st.obj.source.to_s).to eq(%{
226
+ BT /Helvetica 35 Tf 10 20 Td (thirty here) Tj ET
227
+ BT /Helvetica 35 Tf 40 50 Td (sixty there) Tj ET
228
+ }.strip)
229
+
230
+ d = Podoff.parse(@d.write(:string))
231
+
232
+ expect(d.source.index('<< /Length 97 >>')).to eq(618)
233
+ expect(d.xref).to eq(759)
234
+ end
235
+
236
+ it 'returns the open stream when no arg given' do
237
+
238
+ st = @d.add_stream
239
+
240
+ expect(st.class).to eq(Podoff::Stream)
241
+ end
242
+ end
243
+
244
+ describe '#re_add' do
245
+
246
+ it 'replicates an obj and adds the replica to the document' do
247
+
248
+ pa = @d.page(1)
249
+ re = @d.re_add(pa)
250
+
251
+ expect(@d.additions.size).to eq(1)
252
+ expect(@d.objs.keys).to eq((1..6).map { |i| "#{i} 0" })
253
+ expect(@d.additions.keys).to eq([ '3 0' ])
254
+
255
+ expect(re.document).to eq(@d)
256
+ expect(re.ref).to eq(pa.ref)
257
+ expect(re.source).to eq(pa.source)
258
+ expect(re.source).not_to equal(pa.source)
259
+ end
260
+
261
+ it 'accepts a ref' do
262
+
263
+ pa = @d.page(1)
264
+ re = @d.re_add(pa.ref)
265
+
266
+ expect(@d.additions.size).to eq(1)
267
+ expect(@d.objs.keys).to eq((1..6).map { |i| "#{i} 0" })
268
+ expect(@d.additions.keys).to eq([ '3 0' ])
89
269
 
90
- expect(d.objs.values.first.document).to eq(d)
91
- expect(@d.objs.values.first.document).to eq(@d)
270
+ expect(re.document).to eq(@d)
271
+ expect(re.ref).to eq(pa.ref)
272
+ expect(re.source).to eq(pa.source)
273
+ expect(re.source).not_to equal(pa.source)
274
+ end
92
275
  end
93
276
  end
94
277
  end
data/spec/obj_spec.rb CHANGED
@@ -23,160 +23,214 @@ describe Podoff::Obj do
23
23
  end
24
24
  end
25
25
 
26
- describe '#type' do
26
+ describe '#source' do
27
27
 
28
- it 'returns the type of the obj' do
28
+ it 'returns the source behind the obj' do
29
29
 
30
- expect(@d.objs['23 0'].type).to eq('Font')
31
- end
32
-
33
- it 'returns nil if there is no type' do
30
+ o = @d.objs['20 0']
34
31
 
35
- expect(@d.objs['17 0'].type).to eq(nil)
32
+ expect(o.source).to eq(%{
33
+ 20 0 obj [21 0 R]
34
+ endobj
35
+ }.strip)
36
36
  end
37
37
  end
38
38
 
39
- describe '#parent' do
40
-
41
- it 'returns the parent ref if any' do
42
-
43
- expect(@d.objs.values.first.parent).to eq('2 0')
44
- end
45
-
46
- it 'returns nil if there is no parent' do
47
-
48
- expect(@d.objs['2 0'].parent).to eq(nil)
49
- end
50
- end
39
+ # describe '#match' do
40
+ #
41
+ # it 'returns a MatchData instance if there is a match' do
42
+ #
43
+ # o = @d.objs['1 0']
44
+ #
45
+ # m = o.match(/\/Contents ([^\n]+)/)
46
+ #
47
+ # expect(m).not_to eq(nil)
48
+ # expect(m[1]).to eq('3 0 R')
49
+ # expect(m.offset(0)).to eq([ 123, 138 ]) # /!\
50
+ # end
51
+ #
52
+ # it 'returns nil if the match exits the obj' do
53
+ #
54
+ # o = @d.objs['1 0']
55
+ #
56
+ # m = o.match(/3 0 obj/)
57
+ #
58
+ # expect(m).to eq(nil)
59
+ # end
60
+ #
61
+ # it 'returns nil if there is no match' do
62
+ #
63
+ # o = @d.objs['1 0']
64
+ #
65
+ # m = o.match(/nada/)
66
+ #
67
+ # expect(m).to eq(nil)
68
+ # end
69
+ # end
70
+ #
71
+ # describe '#dmatch' do
72
+ #
73
+ # it 'matches with the zero offset set to the document' do
74
+ #
75
+ # o = @d.objs['1 0']
76
+ #
77
+ # m = o.dmatch(/\/Contents ([^\n]+)/)
78
+ #
79
+ # expect(m).not_to eq(nil)
80
+ # expect(m[1]).to eq('3 0 R')
81
+ # expect(m.offset(0)).to eq([ 138, 153 ]) # /!\
82
+ # end
83
+ # end
51
84
 
52
- describe '#kids' do
85
+ describe '#type' do
53
86
 
54
- it 'returns a list of refs' do
87
+ it 'returns the type of the obj' do
55
88
 
56
- expect(@d.objs['2 0'].kids).to eq([ '1 0', '16 0', '33 0' ])
89
+ expect(@d.objs['23 0'].type).to eq('/Font')
57
90
  end
58
91
 
59
- it 'returns an empty list if there are no kids' do
92
+ it 'returns nil if there is no type' do
60
93
 
61
- expect(@d.objs['224 0'].kids).to eq([])
94
+ expect(@d.objs['17 0'].type).to eq(nil)
62
95
  end
63
- end
64
96
 
65
- describe '#contents' do
97
+ it 'works on open streams' do
66
98
 
67
- it 'returns the Contents reference' do
99
+ st = @d.add_stream
68
100
 
69
- expect(@d.objs['1 0'].contents).to eq('3 0')
70
- end
71
-
72
- it 'returns nil if none' do
73
-
74
- expect(@d.objs['224 0'].contents).to eq(nil)
101
+ expect(st.obj.type).to eq(nil)
75
102
  end
76
103
  end
77
104
 
78
- describe '#font_names' do
79
-
80
- it 'returns a list of font names visible in this obj' do
81
-
82
- expect(
83
- @d.objs.values.first.font_names
84
- ).to eq(%w[
85
- C2_0 TT2 TT1 TT0 C2_2 C2_1 Helv
86
- ])
87
- end
88
- end
105
+ # describe '#parent' do
106
+ #
107
+ # it 'returns the parent ref if any' do
108
+ #
109
+ # expect(@d.objs.values.first.parent).to eq('2 0')
110
+ # end
111
+ #
112
+ # it 'returns nil if there is no parent' do
113
+ #
114
+ # expect(@d.objs['2 0'].parent).to eq(nil)
115
+ # end
116
+ # end
89
117
 
90
- describe '#index' do
118
+ # describe '#kids' do
119
+ #
120
+ # it 'returns a list of refs' do
121
+ #
122
+ # expect(@d.objs['2 0'].kids).to eq([ '1 0', '16 0', '33 0' ])
123
+ # end
124
+ #
125
+ # it 'returns an empty list if there are no kids' do
126
+ #
127
+ # expect(@d.objs['224 0'].kids).to eq([])
128
+ # end
129
+ # end
130
+ #
131
+ # describe '#contents' do
132
+ #
133
+ # it 'returns the Contents references (single)' do
134
+ #
135
+ # expect(@d.objs['1 0'].contents).to eq([ '3 0' ])
136
+ # end
137
+ #
138
+ # it 'returns the Contents references (array)' do
139
+ #
140
+ # expect(@d.objs['16 0'].contents).to eq([ '17 0' ])
141
+ # end
142
+ #
143
+ # it 'returns an empty list if none' do
144
+ #
145
+ # expect(@d.objs['224 0'].contents).to eq([])
146
+ # end
147
+ # end
91
148
 
92
- it 'returns the for a given line' do
149
+ context 'insertions' do
93
150
 
94
- o = @d.objs['3 0']
151
+ before :each do
95
152
 
96
- expect(o.index('stream')).to eq(4)
97
- expect(o.index('BT')).to eq(14)
153
+ @d = Podoff.load('pdfs/udocument0.pdf')
98
154
  end
99
155
 
100
- it 'returns nil when it doesn\'t find' do
156
+ describe '#insert_contents' do
101
157
 
102
- o = @d.objs['3 0']
158
+ it 'fails if the target hasn\'t been replicated' do
103
159
 
104
- expect(o.index('nada')).to eq(nil)
105
- end
160
+ expect {
161
+ @d.objs['23 0'].insert_contents('-1 0')
162
+ }.to raise_error(ArgumentError, "target '23 0' not a replica")
163
+ end
106
164
 
107
- it 'accepts regexes' do
165
+ it 'fails if the target doesn\'t have /Contents' do
108
166
 
109
- o = @d.objs['1 0']
167
+ expect {
168
+ ta = @d.re_add('23 0')
169
+ ta.insert_contents('-1 0')
170
+ }.to raise_error(ArgumentError, "target '23 0' doesn't have /Contents")
171
+ end
110
172
 
111
- i = o.index(/^\/B.+Box /)
173
+ it 'accepts an obj' do
112
174
 
113
- expect(i).to eq(40)
114
- expect(o.lines[i]).to eq('/BleedBox [0.0 0.0 612.0 792.0]')
115
- end
116
-
117
- it 'accepts a start index' do
175
+ pa = @d.re_add(@d.page(1))
118
176
 
119
- o = @d.objs['1 0']
177
+ st = @d.add_stream('BT 70 80 Td /Font0 35 Tf (content is king!) Tj ET')
120
178
 
121
- i = o.index(/^\/.+Box /, 3)
179
+ pa.insert_contents(st)
122
180
 
123
- expect(i).to eq(5)
124
- expect(o.lines[i]).to eq('/TrimBox [0.0 0.0 612.0 792.0]')
125
- end
126
- end
181
+ expect(pa.source).to match(/\/Contents \[3 0 R #{st.ref} R\]\n/)
182
+ end
127
183
 
128
- describe '#find' do
184
+ it 'accepts an obj ref' do
129
185
 
130
- it 'returns the first sub obj that matches the given block' do
186
+ pa = @d.re_add(@d.page(1))
131
187
 
132
- o = @d.objs['1 0']
188
+ st = @d.add_stream('BT 70 80 Td /Font0 35 Tf (content is king!) Tj ET')
133
189
 
134
- o1 = o.find { |o| o.index('stream') }
190
+ pa.insert_contents(st.ref)
135
191
 
136
- expect(o1).not_to eq(nil)
137
- expect(o1.lines.first).to eq('3 0 obj ')
192
+ expect(pa.source).to match(/\/Contents \[3 0 R #{st.ref} R\]\n/)
193
+ end
138
194
  end
139
195
 
140
- it 'accept a :skip_root option'
141
- end
142
-
143
- describe '#gather' do
144
-
145
- it 'returns a list of sub obj that match the given block'
146
- it 'accept a :skip_root option'
147
- end
196
+ describe '#insert_font' do
148
197
 
149
- describe '#crop_box' do
198
+ it 'fails if the target hasn\'t been replicated' do
150
199
 
151
- it 'returns the [ x, y, w, h ] box for the obj' do
200
+ expect {
201
+ @d.objs['23 0'].insert_font('/Helvetica', '-1 0')
202
+ }.to raise_error(ArgumentError, "target '23 0' not a replica")
203
+ end
152
204
 
153
- o = @d.objs['1 0']
205
+ it 'accepts name, obj' do
154
206
 
155
- expect(o.crop_box).to eq([ 0.0, 0.0, 612.0, 792.0 ])
156
- end
207
+ fo = @d.add_base_font('/Helvetica')
208
+ pa = @d.re_add(@d.page(1))
157
209
 
158
- it 'defaults to the MediaBox' do
210
+ pa.insert_font('MyHelv', fo)
159
211
 
160
- o = @d.objs['16 0']
212
+ expect(pa.source).to match(/\/Font\s+<<\s+\/MyHelv #{fo.ref} R\s+/)
213
+ end
161
214
 
162
- expect(o.crop_box).to eq([ 0.0, 0.0, 612.0, 792.0 ])
163
- end
164
- end
215
+ it 'accepts name, obj ref' do
165
216
 
166
- describe '#crop_dims' do
217
+ fo = @d.add_base_font('/Helvetica')
218
+ pa = @d.re_add(@d.page(1))
167
219
 
168
- it 'returns [ w, h ]' do
220
+ pa.insert_font('MyHelv', fo.ref)
169
221
 
170
- o = @d.objs['1 0']
222
+ expect(pa.source).to match(/\/Font\s+<<\s+\/MyHelv #{fo.ref} R\s+/)
223
+ end
171
224
 
172
- expect(o.crop_dims).to eq([ 612.0, 792.0 ])
173
- end
225
+ it 'accepts a slash in front of the name' do
174
226
 
175
- it 'defaults to the MediaBox' do
227
+ fo = @d.add_base_font('/Helvetica')
228
+ pa = @d.re_add(@d.page(1))
176
229
 
177
- o = @d.objs['16 0']
230
+ pa.insert_font('/MyHelv', fo.ref)
178
231
 
179
- expect(o.crop_dims).to eq([ 612.0, 792.0 ])
232
+ expect(pa.source).to match(/\/Font\s+<<\s+\/MyHelv #{fo.ref} R\s+/)
233
+ end
180
234
  end
181
235
  end
182
236
  end
@@ -0,0 +1,67 @@
1
+
2
+ #
3
+ # specifying podoff
4
+ #
5
+ # Fri Oct 23 08:36:38 JST 2015
6
+ #
7
+
8
+ require 'spec_helper'
9
+
10
+
11
+ describe Podoff::Stream do
12
+
13
+ describe '#tf' do
14
+
15
+ it 'sets the current font' do
16
+
17
+ st = Podoff::Stream.new
18
+
19
+ st.tf('/Helvetica', 35)
20
+ st.bt(10, 20, 'helvetic')
21
+ st.tf('/ZapfDingbats', 21)
22
+ st.bt(10, 50, 'zapfesque')
23
+
24
+ expect(st.to_s).to eq(%{
25
+ BT /Helvetica 35 Tf 10 20 Td (helvetic) Tj ET
26
+ BT /ZapfDingbats 21 Tf 10 50 Td (zapfesque) Tj ET
27
+ }.strip)
28
+ end
29
+ end
30
+
31
+ describe '#bt' do
32
+
33
+ it 'works' do
34
+
35
+ st = Podoff::Stream.new
36
+ st.bt(10, 20, 'hello world')
37
+
38
+ expect(st.to_s).to eq('BT 10 20 Td (hello world) Tj ET')
39
+ end
40
+
41
+ it 'escapes the text' do
42
+
43
+ st = Podoff::Stream.new
44
+ st.bt(10, 20, 'hello()world')
45
+
46
+ expect(st.to_s).to eq('BT 10 20 Td (hello\(\)world) Tj ET')
47
+ end
48
+ end
49
+
50
+ describe '#write' do
51
+
52
+ it 'injects text into the stream' do
53
+
54
+ st = Podoff::Stream.new
55
+ st.bt(10, 20, 'abc')
56
+ st.write("\nBT 25 35 Td (ABC) Tj ET")
57
+ st.bt(30, 40, 'def')
58
+
59
+ expect(st.to_s).to eq(%{
60
+ BT 10 20 Td (abc) Tj ET
61
+ BT 25 35 Td (ABC) Tj ET
62
+ BT 30 40 Td (def) Tj ET
63
+ }.strip)
64
+ end
65
+ end
66
+ end
67
+