origami 1.0.4 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +40 -49
- data/bin/gui/about.rb +1 -1
- data/bin/pdfencrypt +10 -4
- data/origami/actions.rb +0 -1
- data/origami/docmdp.rb +96 -0
- data/origami/encryption.rb +103 -14
- data/origami/graphics.rb +1 -0
- data/origami/graphics/colors.rb +23 -23
- data/origami/graphics/instruction.rb +7 -29
- data/origami/graphics/path.rb +53 -19
- data/origami/graphics/render.rb +69 -0
- data/origami/graphics/state.rb +9 -9
- data/origami/graphics/text.rb +66 -48
- data/origami/graphics/xobject.rb +65 -50
- data/origami/javascript.rb +135 -0
- data/origami/metadata.rb +24 -0
- data/origami/object.rb +1 -1
- data/origami/page.rb +12 -0
- data/origami/parsers/pdf/linear.rb +0 -0
- data/origami/pdf.rb +2 -2
- metadata +77 -75
- data/VERSION +0 -1
data/origami/graphics/xobject.rb
CHANGED
@@ -41,19 +41,30 @@ module Origami
|
|
41
41
|
DEFAULT_LINEWIDTH = 1.0
|
42
42
|
|
43
43
|
attr_reader :instructions
|
44
|
+
attr_accessor :canvas
|
44
45
|
|
45
46
|
def initialize(rawdata = "", dictionary = {})
|
46
47
|
|
47
48
|
@instructions = nil
|
48
|
-
@
|
49
|
+
@canvas = Graphics::DummyCanvas.new
|
49
50
|
|
50
51
|
super(rawdata, dictionary)
|
51
52
|
end
|
52
53
|
|
54
|
+
def render(engine)
|
55
|
+
load! if @instructions.nil?
|
56
|
+
|
57
|
+
@instructions.each do |instruction|
|
58
|
+
instruction.render(engine)
|
59
|
+
end
|
60
|
+
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
53
64
|
def pre_build #:nodoc:
|
54
65
|
load! if @instructions.nil?
|
55
|
-
if @gs.text_state.is_in_text_object?
|
56
|
-
@instructions << PDF::Instruction.new('ET').
|
66
|
+
if @canvas.gs.text_state.is_in_text_object?
|
67
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
57
68
|
end
|
58
69
|
|
59
70
|
@data = @instructions.join
|
@@ -99,16 +110,16 @@ module Origami
|
|
99
110
|
set_line_join(line_join)
|
100
111
|
set_dash_pattern(dash_pattern)
|
101
112
|
|
102
|
-
if @gs.text_state.is_in_text_object?
|
103
|
-
@instructions << PDF::Instruction.new('ET').
|
113
|
+
if @canvas.gs.text_state.is_in_text_object?
|
114
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
104
115
|
end
|
105
116
|
|
106
117
|
unless coords.size < 1
|
107
118
|
x,y = coords.slice!(0)
|
108
|
-
@instructions << PDF::Instruction.new('m',x,y).
|
119
|
+
@instructions << PDF::Instruction.new('m',x,y).render(@canvas)
|
109
120
|
|
110
121
|
coords.each do |px,py|
|
111
|
-
@instructions << PDF::Instruction.new('l',px,py).
|
122
|
+
@instructions << PDF::Instruction.new('l',px,py).render(@canvas)
|
112
123
|
end
|
113
124
|
|
114
125
|
@instructions << (i =
|
@@ -121,7 +132,7 @@ module Origami
|
|
121
132
|
end
|
122
133
|
)
|
123
134
|
|
124
|
-
i.
|
135
|
+
i.render(@canvas)
|
125
136
|
end
|
126
137
|
|
127
138
|
self
|
@@ -152,11 +163,11 @@ module Origami
|
|
152
163
|
set_line_join(line_join)
|
153
164
|
set_dash_pattern(dash_pattern)
|
154
165
|
|
155
|
-
if @gs.text_state.is_in_text_object?
|
156
|
-
@instructions << PDF::Instruction.new('ET').
|
166
|
+
if @canvas.gs.text_state.is_in_text_object?
|
167
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas)
|
157
168
|
end
|
158
169
|
|
159
|
-
@instructions << PDF::Instruction.new('re', x,y,width,height).
|
170
|
+
@instructions << PDF::Instruction.new('re', x,y,width,height).render(@canvas)
|
160
171
|
|
161
172
|
@instructions << (i =
|
162
173
|
if stroke and not fill
|
@@ -168,7 +179,7 @@ module Origami
|
|
168
179
|
end
|
169
180
|
)
|
170
181
|
|
171
|
-
i.
|
182
|
+
i.render(@canvas)
|
172
183
|
|
173
184
|
self
|
174
185
|
end
|
@@ -194,10 +205,10 @@ module Origami
|
|
194
205
|
rise = attr[:rise]
|
195
206
|
rendering = attr[:rendering]
|
196
207
|
|
197
|
-
@instructions << PDF::Instruction.new('ET').
|
208
|
+
@instructions << PDF::Instruction.new('ET').render(@canvas) if (x or y) and @canvas.gs.text_state.is_in_text_object?
|
198
209
|
|
199
|
-
unless @gs.text_state.is_in_text_object?
|
200
|
-
@instructions << PDF::Instruction.new('BT').
|
210
|
+
unless @canvas.gs.text_state.is_in_text_object?
|
211
|
+
@instructions << PDF::Instruction.new('BT').render(@canvas)
|
201
212
|
end
|
202
213
|
|
203
214
|
set_text_font(font, size)
|
@@ -219,15 +230,15 @@ module Origami
|
|
219
230
|
|
220
231
|
def paint_shading(shade)
|
221
232
|
load! if @instructions.nil?
|
222
|
-
@instructions << PDF::Instruction.new('sh', shade).
|
233
|
+
@instructions << PDF::Instruction.new('sh', shade).render(@canvas)
|
223
234
|
|
224
235
|
self
|
225
236
|
end
|
226
237
|
|
227
238
|
def set_text_font(fontname, size)
|
228
239
|
load! if @instructions.nil?
|
229
|
-
if fontname != @gs.text_state.font or size != @gs.text_state.font_size
|
230
|
-
@instructions << PDF::Instruction.new('Tf', fontname, size).
|
240
|
+
if fontname != @canvas.gs.text_state.font or size != @canvas.gs.text_state.font_size
|
241
|
+
@instructions << PDF::Instruction.new('Tf', fontname, size).render(@canvas)
|
231
242
|
end
|
232
243
|
|
233
244
|
self
|
@@ -235,15 +246,15 @@ module Origami
|
|
235
246
|
|
236
247
|
def set_text_pos(tx,ty)
|
237
248
|
load! if @instructions.nil?
|
238
|
-
@instructions << PDF::Instruction.new('Td', tx, ty).
|
249
|
+
@instructions << PDF::Instruction.new('Td', tx, ty).render(@canvas)
|
239
250
|
|
240
251
|
self
|
241
252
|
end
|
242
253
|
|
243
254
|
def set_text_leading(leading)
|
244
255
|
load! if @instructions.nil?
|
245
|
-
if leading != @gs.text_state.leading
|
246
|
-
@instructions << PDF::Instruction.new('TL', leading).
|
256
|
+
if leading != @canvas.gs.text_state.leading
|
257
|
+
@instructions << PDF::Instruction.new('TL', leading).render(@canvas)
|
247
258
|
end
|
248
259
|
|
249
260
|
self
|
@@ -251,8 +262,8 @@ module Origami
|
|
251
262
|
|
252
263
|
def set_text_rendering(rendering)
|
253
264
|
load! if @instructions.nil?
|
254
|
-
if rendering != @gs.text_state.rendering_mode
|
255
|
-
@instructions << PDF::Instruction.new('Tr', rendering).
|
265
|
+
if rendering != @canvas.gs.text_state.rendering_mode
|
266
|
+
@instructions << PDF::Instruction.new('Tr', rendering).render(@canvas)
|
256
267
|
end
|
257
268
|
|
258
269
|
self
|
@@ -260,8 +271,8 @@ module Origami
|
|
260
271
|
|
261
272
|
def set_text_rise(rise)
|
262
273
|
load! if @instructions.nil?
|
263
|
-
if rise != @gs.text_state.text_rise
|
264
|
-
@instructions << PDF::Instruction.new('Ts', rise).
|
274
|
+
if rise != @canvas.gs.text_state.text_rise
|
275
|
+
@instructions << PDF::Instruction.new('Ts', rise).render(@canvas)
|
265
276
|
end
|
266
277
|
|
267
278
|
self
|
@@ -269,8 +280,8 @@ module Origami
|
|
269
280
|
|
270
281
|
def set_text_scale(scaling)
|
271
282
|
load! if @instructions.nil?
|
272
|
-
if scale != @gs.text_state.scaling
|
273
|
-
@instructions << PDF::Instruction.new('Tz', scaling).
|
283
|
+
if scale != @canvas.gs.text_state.scaling
|
284
|
+
@instructions << PDF::Instruction.new('Tz', scaling).render(@canvas)
|
274
285
|
end
|
275
286
|
|
276
287
|
self
|
@@ -278,8 +289,8 @@ module Origami
|
|
278
289
|
|
279
290
|
def set_text_word_spacing(word_spacing)
|
280
291
|
load! if @instructions.nil?
|
281
|
-
if word_spacing != @gs.text_state.word_spacing
|
282
|
-
@instructions << PDF::Instruction.new('Tw', word_spacing).
|
292
|
+
if word_spacing != @canvas.gs.text_state.word_spacing
|
293
|
+
@instructions << PDF::Instruction.new('Tw', word_spacing).render(@canvas)
|
283
294
|
end
|
284
295
|
|
285
296
|
self
|
@@ -287,8 +298,8 @@ module Origami
|
|
287
298
|
|
288
299
|
def set_text_char_spacing(char_spacing)
|
289
300
|
load! if @instructions.nil?
|
290
|
-
if char_spacing != @gs.text_state.char_spacing
|
291
|
-
@instructions << PDF::Instruction.new('Tc', char_spacing).
|
301
|
+
if char_spacing != @canvas.gs.text_state.char_spacing
|
302
|
+
@instructions << PDF::Instruction.new('Tc', char_spacing).render(@canvas)
|
292
303
|
end
|
293
304
|
|
294
305
|
self
|
@@ -302,25 +313,25 @@ module Origami
|
|
302
313
|
r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
|
303
314
|
g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
|
304
315
|
b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
|
305
|
-
PDF::Instruction.new('rg', r, g, b) if @gs.nonstroking_color != [r,g,b]
|
316
|
+
PDF::Instruction.new('rg', r, g, b) if @canvas.gs.nonstroking_color != [r,g,b]
|
306
317
|
|
307
318
|
elsif (color.respond_to? :c and color.respond_to? :m and color.respond_to? :y and color.respond_to? :k) or (color.is_a?(::Array) and color.size == 4)
|
308
319
|
c = (color.respond_to?(:c) ? color.c : color[0]).to_f
|
309
320
|
m = (color.respond_to?(:m) ? color.m : color[1]).to_f
|
310
321
|
y = (color.respond_to?(:y) ? color.y : color[2]).to_f
|
311
322
|
k = (color.respond_to?(:k) ? color.k : color[3]).to_f
|
312
|
-
PDF::Instruction.new('k', c, m, y, k) if @gs.nonstroking_color != [c,m,y,k]
|
323
|
+
PDF::Instruction.new('k', c, m, y, k) if @canvas.gs.nonstroking_color != [c,m,y,k]
|
313
324
|
|
314
325
|
elsif color.respond_to?:g or (0.0..1.0) === color
|
315
326
|
g = color.respond_to?(:g) ? color.g : color
|
316
|
-
PDF::Instruction.new('g', g) if @gs.nonstroking_color != [ g ]
|
327
|
+
PDF::Instruction.new('g', g) if @canvas.gs.nonstroking_color != [ g ]
|
317
328
|
|
318
329
|
else
|
319
330
|
raise TypeError, "Invalid color : #{color}"
|
320
331
|
end
|
321
332
|
)
|
322
333
|
|
323
|
-
i.
|
334
|
+
i.render(@canvas) if i
|
324
335
|
self
|
325
336
|
end
|
326
337
|
|
@@ -332,32 +343,32 @@ module Origami
|
|
332
343
|
r = (color.respond_to?(:r) ? color.r : color[0]).to_f / 255
|
333
344
|
g = (color.respond_to?(:g) ? color.g : color[1]).to_f / 255
|
334
345
|
b = (color.respond_to?(:b) ? color.b : color[2]).to_f / 255
|
335
|
-
PDF::Instruction.new('RG', r, g, b) if @gs.stroking_color != [r,g,b]
|
346
|
+
PDF::Instruction.new('RG', r, g, b) if @canvas.gs.stroking_color != [r,g,b]
|
336
347
|
|
337
348
|
elsif (color.respond_to? :c and color.respond_to? :m and color.respond_to? :y and color.respond_to? :k) or (color.is_a?(::Array) and color.size == 4)
|
338
349
|
c = (color.respond_to?(:c) ? color.c : color[0]).to_f
|
339
350
|
m = (color.respond_to?(:m) ? color.m : color[1]).to_f
|
340
351
|
y = (color.respond_to?(:y) ? color.y : color[2]).to_f
|
341
352
|
k = (color.respond_to?(:k) ? color.k : color[3]).to_f
|
342
|
-
PDF::Instruction.new('K', c, m, y, k) if @gs.stroking_color != [c,m,y,k]
|
353
|
+
PDF::Instruction.new('K', c, m, y, k) if @canvas.gs.stroking_color != [c,m,y,k]
|
343
354
|
|
344
355
|
elsif color.respond_to?:g or (0.0..1.0) === color
|
345
356
|
g = color.respond_to?(:g) ? color.g : color
|
346
|
-
PDF::Instruction.new('G', g) if @gs.stroking_color != [ g ]
|
357
|
+
PDF::Instruction.new('G', g) if @canvas.gs.stroking_color != [ g ]
|
347
358
|
|
348
359
|
else
|
349
360
|
raise TypeError, "Invalid color : #{color}"
|
350
361
|
end
|
351
362
|
)
|
352
363
|
|
353
|
-
i.
|
364
|
+
i.render(@canvas) if i
|
354
365
|
self
|
355
366
|
end
|
356
367
|
|
357
368
|
def set_dash_pattern(pattern)
|
358
369
|
load! if @instructions.nil?
|
359
|
-
unless @gs.dash_pattern.eql? pattern
|
360
|
-
@instructions << PDF::Instruction.new('d', pattern.array, pattern.phase).
|
370
|
+
unless @canvas.gs.dash_pattern.eql? pattern
|
371
|
+
@instructions << PDF::Instruction.new('d', pattern.array, pattern.phase).render(@canvas)
|
361
372
|
end
|
362
373
|
|
363
374
|
self
|
@@ -365,8 +376,8 @@ module Origami
|
|
365
376
|
|
366
377
|
def set_line_width(width)
|
367
378
|
load! if @instructions.nil?
|
368
|
-
if @gs.line_width != width
|
369
|
-
@instructions << PDF::Instruction.new('w', width).
|
379
|
+
if @canvas.gs.line_width != width
|
380
|
+
@instructions << PDF::Instruction.new('w', width).render(@canvas)
|
370
381
|
end
|
371
382
|
|
372
383
|
self
|
@@ -374,8 +385,8 @@ module Origami
|
|
374
385
|
|
375
386
|
def set_line_cap(cap)
|
376
387
|
load! if @instructions.nil?
|
377
|
-
if @gs.line_cap != cap
|
378
|
-
@instructions << PDF::Instruction.new('J', cap).
|
388
|
+
if @canvas.gs.line_cap != cap
|
389
|
+
@instructions << PDF::Instruction.new('J', cap).render(@canvas)
|
379
390
|
end
|
380
391
|
|
381
392
|
self
|
@@ -383,8 +394,8 @@ module Origami
|
|
383
394
|
|
384
395
|
def set_line_join(join)
|
385
396
|
load! if @instructions.nil?
|
386
|
-
if @gs.line_join != join
|
387
|
-
@instructions << PDF::Instruction.new('j', join).
|
397
|
+
if @canvas.gs.line_join != join
|
398
|
+
@instructions << PDF::Instruction.new('j', join).render(@canvas)
|
388
399
|
end
|
389
400
|
|
390
401
|
self
|
@@ -397,7 +408,11 @@ module Origami
|
|
397
408
|
|
398
409
|
code = StringScanner.new self.data
|
399
410
|
@instructions = []
|
400
|
-
|
411
|
+
|
412
|
+
until code.eos?
|
413
|
+
insn = PDF::Instruction.parse(code)
|
414
|
+
@instructions << insn if insn
|
415
|
+
end
|
401
416
|
|
402
417
|
self
|
403
418
|
end
|
@@ -406,9 +421,9 @@ module Origami
|
|
406
421
|
|
407
422
|
lines = text.split("\n").map!{|line| line.to_s}
|
408
423
|
|
409
|
-
@instructions << PDF::Instruction.new('Tj', lines.slice!(0)).
|
424
|
+
@instructions << PDF::Instruction.new('Tj', lines.slice!(0)).render(@canvas)
|
410
425
|
lines.each do |line|
|
411
|
-
@instructions << PDF::Instruction.new("'", line).
|
426
|
+
@instructions << PDF::Instruction.new("'", line).render(@canvas)
|
412
427
|
end
|
413
428
|
|
414
429
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
= File
|
4
|
+
javascript.rb
|
5
|
+
|
6
|
+
= Info
|
7
|
+
This file is part of Origami, PDF manipulation framework for Ruby
|
8
|
+
Copyright (C) 2010 Guillaume Delugré <guillaume@security-labs.org>
|
9
|
+
All right reserved.
|
10
|
+
|
11
|
+
Origami is free software: you can redistribute it and/or modify
|
12
|
+
it under the terms of the GNU Lesser General Public License as published by
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
14
|
+
(at your option) any later version.
|
15
|
+
|
16
|
+
Origami is distributed in the hope that it will be useful,
|
17
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
19
|
+
GNU Lesser General Public License for more details.
|
20
|
+
|
21
|
+
You should have received a copy of the GNU Lesser General Public License
|
22
|
+
along with Origami. If not, see <http://www.gnu.org/licenses/>.
|
23
|
+
|
24
|
+
=end
|
25
|
+
|
26
|
+
module Origami
|
27
|
+
|
28
|
+
class PDF
|
29
|
+
begin
|
30
|
+
require 'johnson'
|
31
|
+
|
32
|
+
class JavaScriptEngine < Johnson::SpiderMonkey::Runtime
|
33
|
+
|
34
|
+
class DocumentObject
|
35
|
+
attr_reader :author, :creator, :creationDate, :keywords, :modDate, :producer, :subject, :title
|
36
|
+
|
37
|
+
def initialize(pdf)
|
38
|
+
@pdf = pdf
|
39
|
+
|
40
|
+
@author = @pdf.author || ''
|
41
|
+
@creator = @pdf.creator || ''
|
42
|
+
@keywords = @pdf.keywords || ''
|
43
|
+
@producer = @pdf.producer || ''
|
44
|
+
@subject = @pdf.subject || ''
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
"[object Doc]"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class ApplicationObject
|
53
|
+
DEFAULT_VIEWER_VERSION = 9
|
54
|
+
|
55
|
+
attr_reader :viewerVersion
|
56
|
+
attr_reader :viewerVariation
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
@viewerVersion = DEFAULT_VIEWER_VERSION
|
60
|
+
@viewerVariation = @viewerType = "Reader"
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_s
|
64
|
+
"[object App]"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class ConsoleObject
|
69
|
+
def println(msg)
|
70
|
+
puts msg.to_s
|
71
|
+
end
|
72
|
+
|
73
|
+
def show; end
|
74
|
+
def clear; end
|
75
|
+
def hide; end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
"[object Console]"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class UtilObject
|
83
|
+
def to_s
|
84
|
+
"[object Util]"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def initialize(pdf, options = {})
|
89
|
+
super()
|
90
|
+
|
91
|
+
self['app'] = ApplicationObject.new
|
92
|
+
if options.has_key?(:viewerVersion)
|
93
|
+
self['app'].instance_variable_set :@viewerVersion, options[:viewerVersion]
|
94
|
+
end
|
95
|
+
|
96
|
+
self['console'] = ConsoleObject.new
|
97
|
+
self['util'] = UtilObject.new
|
98
|
+
|
99
|
+
# Johnson includes the Ruby namespace in the global scope.
|
100
|
+
# Unsets that for obvious security reasons.
|
101
|
+
self['Ruby'] = nil
|
102
|
+
|
103
|
+
# Hack the 'this' object to point to the DocumentObject
|
104
|
+
@doc_obj = self['doc'] = DocumentObject.new(pdf)
|
105
|
+
evaluate('this.doc.eval = function(script) {eval(script)}')
|
106
|
+
evaluate('this.doc = undefined')
|
107
|
+
end
|
108
|
+
|
109
|
+
def exec(script)
|
110
|
+
@doc_obj.eval(script)
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
rescue LoadError
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
if defined?(PDF::JavaScriptEngine)
|
120
|
+
module String
|
121
|
+
def eval_as_js(options = {})
|
122
|
+
runtime = options[:runtime] || PDF::JavaScriptEngine.new(self.pdf, options)
|
123
|
+
runtime.exec(self.value)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
class Stream
|
128
|
+
def eval_as_js(options = {})
|
129
|
+
runtime = options[:runtime] || PDF::JavaScriptEngine.new(self.pdf, options)
|
130
|
+
runtime.exec(self.data)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
data/origami/metadata.rb
CHANGED
@@ -50,6 +50,15 @@ module Origami
|
|
50
50
|
get_doc_attr :Info
|
51
51
|
end
|
52
52
|
|
53
|
+
def title; get_document_info_field(:Title) end
|
54
|
+
def author; get_document_info_field(:Author) end
|
55
|
+
def subject; get_document_info_field(:Subject) end
|
56
|
+
def keywords; get_document_info_field(:Keywords) end
|
57
|
+
def creator; get_document_info_field(:Creator) end
|
58
|
+
def producer; get_document_info_field(:Producer) end
|
59
|
+
def creation_date; get_document_info_field(:CreationDate) end
|
60
|
+
def mod_date; get_document_info_field(:ModDate) end
|
61
|
+
|
53
62
|
#
|
54
63
|
# Returns a Hash of the information found in the metadata stream
|
55
64
|
#
|
@@ -81,6 +90,21 @@ module Origami
|
|
81
90
|
end
|
82
91
|
end
|
83
92
|
|
93
|
+
private
|
94
|
+
|
95
|
+
def get_document_info_field(field) #:nodoc:
|
96
|
+
if has_document_info?
|
97
|
+
doc_info = get_document_info
|
98
|
+
|
99
|
+
if doc_info.has_key?(field)
|
100
|
+
case obj = get_document_info[field].solve
|
101
|
+
when String then return obj.value
|
102
|
+
when Stream then return obj.data
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
84
108
|
end
|
85
109
|
|
86
110
|
#
|