podoff 0.9.0 → 1.0.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/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
+