origami 1.2.7 → 2.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.
Files changed (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +66 -0
  3. data/README.md +112 -0
  4. data/bin/config/pdfcop.conf.yml +232 -233
  5. data/bin/gui/about.rb +27 -37
  6. data/bin/gui/config.rb +108 -117
  7. data/bin/gui/file.rb +416 -365
  8. data/bin/gui/gtkhex.rb +1138 -1153
  9. data/bin/gui/hexview.rb +55 -57
  10. data/bin/gui/imgview.rb +48 -51
  11. data/bin/gui/menu.rb +388 -386
  12. data/bin/gui/properties.rb +114 -130
  13. data/bin/gui/signing.rb +571 -617
  14. data/bin/gui/textview.rb +77 -95
  15. data/bin/gui/treeview.rb +382 -387
  16. data/bin/gui/walker.rb +227 -232
  17. data/bin/gui/xrefs.rb +56 -60
  18. data/bin/pdf2pdfa +53 -57
  19. data/bin/pdf2ruby +212 -228
  20. data/bin/pdfcop +338 -348
  21. data/bin/pdfdecompress +58 -65
  22. data/bin/pdfdecrypt +56 -60
  23. data/bin/pdfencrypt +75 -80
  24. data/bin/pdfexplode +185 -182
  25. data/bin/pdfextract +201 -218
  26. data/bin/pdfmetadata +83 -82
  27. data/bin/pdfsh +4 -5
  28. data/bin/pdfwalker +1 -2
  29. data/bin/shell/.irbrc +45 -82
  30. data/bin/shell/console.rb +105 -130
  31. data/bin/shell/hexdump.rb +40 -64
  32. data/examples/README.md +34 -0
  33. data/examples/attachments/attachment.rb +38 -0
  34. data/examples/attachments/nested_document.rb +51 -0
  35. data/examples/encryption/encryption.rb +28 -0
  36. data/{samples/actions/triggerevents/trigger.rb → examples/events/events.rb} +13 -16
  37. data/examples/flash/flash.rb +37 -0
  38. data/{samples → examples}/flash/helloworld.swf +0 -0
  39. data/examples/forms/javascript.rb +54 -0
  40. data/examples/forms/xfa.rb +115 -0
  41. data/examples/javascript/hello_world.rb +22 -0
  42. data/examples/javascript/js_emulation.rb +54 -0
  43. data/examples/loop/goto.rb +32 -0
  44. data/examples/loop/named.rb +33 -0
  45. data/examples/signature/signature.rb +65 -0
  46. data/examples/uri/javascript.rb +56 -0
  47. data/examples/uri/open-uri.rb +21 -0
  48. data/examples/uri/submitform.rb +47 -0
  49. data/lib/origami.rb +29 -42
  50. data/lib/origami/3d.rb +350 -225
  51. data/lib/origami/acroform.rb +262 -288
  52. data/lib/origami/actions.rb +268 -288
  53. data/lib/origami/annotations.rb +697 -722
  54. data/lib/origami/array.rb +258 -184
  55. data/lib/origami/boolean.rb +74 -84
  56. data/lib/origami/catalog.rb +397 -434
  57. data/lib/origami/collections.rb +144 -0
  58. data/lib/origami/destinations.rb +233 -194
  59. data/lib/origami/dictionary.rb +253 -232
  60. data/lib/origami/encryption.rb +1274 -1243
  61. data/lib/origami/export.rb +232 -268
  62. data/lib/origami/extensions/fdf.rb +307 -220
  63. data/lib/origami/extensions/ppklite.rb +368 -435
  64. data/lib/origami/filespec.rb +197 -0
  65. data/lib/origami/filters.rb +301 -295
  66. data/lib/origami/filters/ascii.rb +177 -180
  67. data/lib/origami/filters/ccitt.rb +528 -535
  68. data/lib/origami/filters/crypt.rb +26 -35
  69. data/lib/origami/filters/dct.rb +46 -52
  70. data/lib/origami/filters/flate.rb +95 -94
  71. data/lib/origami/filters/jbig2.rb +49 -55
  72. data/lib/origami/filters/jpx.rb +38 -44
  73. data/lib/origami/filters/lzw.rb +189 -183
  74. data/lib/origami/filters/predictors.rb +221 -235
  75. data/lib/origami/filters/runlength.rb +103 -104
  76. data/lib/origami/font.rb +173 -186
  77. data/lib/origami/functions.rb +67 -81
  78. data/lib/origami/graphics.rb +25 -21
  79. data/lib/origami/graphics/colors.rb +178 -187
  80. data/lib/origami/graphics/instruction.rb +79 -85
  81. data/lib/origami/graphics/path.rb +142 -148
  82. data/lib/origami/graphics/patterns.rb +160 -167
  83. data/lib/origami/graphics/render.rb +43 -50
  84. data/lib/origami/graphics/state.rb +138 -153
  85. data/lib/origami/graphics/text.rb +188 -205
  86. data/lib/origami/graphics/xobject.rb +819 -815
  87. data/lib/origami/header.rb +63 -78
  88. data/lib/origami/javascript.rb +596 -597
  89. data/lib/origami/linearization.rb +285 -290
  90. data/lib/origami/metadata.rb +139 -148
  91. data/lib/origami/name.rb +112 -148
  92. data/lib/origami/null.rb +53 -62
  93. data/lib/origami/numeric.rb +162 -175
  94. data/lib/origami/obfuscation.rb +186 -174
  95. data/lib/origami/object.rb +593 -573
  96. data/lib/origami/outline.rb +42 -47
  97. data/lib/origami/outputintents.rb +73 -82
  98. data/lib/origami/page.rb +703 -592
  99. data/lib/origami/parser.rb +238 -290
  100. data/lib/origami/parsers/fdf.rb +41 -33
  101. data/lib/origami/parsers/pdf.rb +75 -95
  102. data/lib/origami/parsers/pdf/lazy.rb +137 -0
  103. data/lib/origami/parsers/pdf/linear.rb +64 -66
  104. data/lib/origami/parsers/ppklite.rb +34 -70
  105. data/lib/origami/pdf.rb +1030 -1005
  106. data/lib/origami/reference.rb +102 -102
  107. data/lib/origami/signature.rb +591 -609
  108. data/lib/origami/stream.rb +668 -551
  109. data/lib/origami/string.rb +397 -373
  110. data/lib/origami/template/patterns.rb +56 -0
  111. data/lib/origami/template/widgets.rb +151 -0
  112. data/lib/origami/trailer.rb +144 -158
  113. data/lib/origami/tree.rb +62 -0
  114. data/lib/origami/version.rb +23 -0
  115. data/lib/origami/webcapture.rb +88 -79
  116. data/lib/origami/xfa.rb +2863 -2882
  117. data/lib/origami/xreftable.rb +472 -384
  118. data/test/dataset/calc.pdf +85 -0
  119. data/test/dataset/crypto.pdf +82 -0
  120. data/test/dataset/empty.pdf +49 -0
  121. data/test/test_actions.rb +27 -0
  122. data/test/test_annotations.rb +90 -0
  123. data/test/test_pages.rb +31 -0
  124. data/test/test_pdf.rb +16 -0
  125. data/test/test_pdf_attachment.rb +34 -0
  126. data/test/test_pdf_create.rb +24 -0
  127. data/test/test_pdf_encrypt.rb +95 -0
  128. data/test/test_pdf_parse.rb +96 -0
  129. data/test/test_pdf_sign.rb +58 -0
  130. data/test/test_streams.rb +182 -0
  131. data/test/test_xrefs.rb +67 -0
  132. metadata +88 -58
  133. data/README +0 -67
  134. data/bin/pdf2graph +0 -121
  135. data/bin/pdfcocoon +0 -104
  136. data/lib/origami/file.rb +0 -233
  137. data/samples/README.txt +0 -45
  138. data/samples/actions/launch/calc.rb +0 -87
  139. data/samples/actions/launch/winparams.rb +0 -22
  140. data/samples/actions/loop/loopgoto.rb +0 -24
  141. data/samples/actions/loop/loopnamed.rb +0 -21
  142. data/samples/actions/named/named.rb +0 -31
  143. data/samples/actions/samba/smbrelay.rb +0 -26
  144. data/samples/actions/webbug/submitform.js +0 -26
  145. data/samples/actions/webbug/webbug-browser.rb +0 -68
  146. data/samples/actions/webbug/webbug-js.rb +0 -67
  147. data/samples/actions/webbug/webbug-reader.rb +0 -90
  148. data/samples/attachments/attach.rb +0 -40
  149. data/samples/attachments/attached.txt +0 -1
  150. data/samples/crypto/crypto.rb +0 -28
  151. data/samples/digsig/signed.rb +0 -46
  152. data/samples/exploits/cve-2008-2992-utilprintf.rb +0 -87
  153. data/samples/exploits/cve-2009-0927-geticon.rb +0 -65
  154. data/samples/exploits/exploit_customdictopen.rb +0 -55
  155. data/samples/exploits/getannots.rb +0 -69
  156. data/samples/flash/flash.rb +0 -31
  157. data/samples/javascript/attached.txt +0 -1
  158. data/samples/javascript/js.rb +0 -52
  159. data/templates/patterns.rb +0 -66
  160. data/templates/widgets.rb +0 -173
  161. data/templates/xdp.rb +0 -92
  162. data/test/ts_pdf.rb +0 -50
@@ -1,210 +1,284 @@
1
1
  =begin
2
2
 
3
- = File
4
- array.rb
5
-
6
- = Info
7
- This file is part of Origami, PDF manipulation framework for Ruby
8
- Copyright (C) 2010 Guillaume DelugrÈ <guillaume AT security-labs DOT 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/>.
3
+ This file is part of Origami, PDF manipulation framework for Ruby
4
+ Copyright (C) 2016 Guillaume Delugré.
23
5
 
24
- =end
6
+ Origami is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Lesser General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
25
10
 
11
+ Origami is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Lesser General Public License for more details.
26
15
 
27
- module Origami
16
+ You should have received a copy of the GNU Lesser General Public License
17
+ along with Origami. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ =end
28
20
 
29
- class InvalidArrayObjectError < InvalidObjectError #:nodoc:
30
- end
31
21
 
32
- #
33
- # Class representing an Array Object.
34
- # Arrays contain a set of Object.
35
- #
36
- class Array < ::Array
37
- include Origami::Object
38
-
39
- TOKENS = %w{ [ ] } #:nodoc:
40
- @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first) + WHITESPACES)
41
- @@regexp_close = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.last) + WHITESPACES)
22
+ module Origami
42
23
 
43
- attr_reader :strings_cache, :names_cache, :xref_cache
24
+ class InvalidArrayObjectError < InvalidObjectError #:nodoc:
25
+ end
44
26
 
45
27
  #
46
- # Creates a new PDF Array Object.
47
- # _data_:: An array of objects.
28
+ # Class representing an Array Object.
29
+ # Arrays contain a set of Object.
48
30
  #
49
- def initialize(data = [])
50
- raise TypeError, "Expected type Array, received #{data.class}." unless data.is_a?(::Array)
51
- super()
52
-
53
- @strings_cache = []
54
- @names_cache = []
55
- @xref_cache = {}
56
-
57
- i = 0
58
- while i < data.size
59
- case val = data[i].to_o
60
- when String then @strings_cache.push(val)
61
- when Name then @names_cache.push(val)
62
- when Reference then
63
- (@xref_cache[val] ||= []).push(self)
64
- when Dictionary,Array then
65
- @strings_cache.concat(val.strings_cache)
66
- @names_cache.concat(val.names_cache)
67
- @xref_cache.update(val.xref_cache) do |ref, cache1, cache2|
68
- cache1.concat(cache2)
31
+ class Array < ::Array
32
+ include Origami::Object
33
+ using TypeConversion
34
+
35
+ TOKENS = %w{ [ ] } #:nodoc:
36
+ @@regexp_open = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.first) + WHITESPACES)
37
+ @@regexp_close = Regexp.new(WHITESPACES + Regexp.escape(TOKENS.last) + WHITESPACES)
38
+
39
+ attr_reader :strings_cache, :names_cache, :xref_cache
40
+
41
+ #
42
+ # Creates a new PDF Array Object.
43
+ # _data_:: An array of objects.
44
+ #
45
+ def initialize(data = [], parser = nil, hint_type: nil)
46
+ raise TypeError, "Expected type Array, received #{data.class}." unless data.is_a?(::Array)
47
+ super()
48
+
49
+ @strings_cache = []
50
+ @names_cache = []
51
+ @xref_cache = {}
52
+
53
+ data.each_with_index do |value, index|
54
+ value = value.to_o
55
+
56
+ if Origami::OPTIONS[:enable_type_guessing]
57
+ index_type = hint_type.is_a?(::Array) ? hint_type[index % hint_type.size] : hint_type
58
+ if index_type.is_a?(::Array) and not value.is_a?(Reference)
59
+ index_type = index_type.find {|type| type < value.class }
60
+ end
61
+
62
+ if index_type.is_a?(Class) and index_type < value.class
63
+ value = value.cast_to(index_type, parser)
64
+ end
65
+
66
+ if index_type and parser and Origami::OPTIONS[:enable_type_propagation]
67
+ if value.is_a?(Reference)
68
+ parser.defer_type_cast(value, index_type)
69
+ end
70
+ end
71
+ end
72
+
73
+ # Cache object value for fast search.
74
+ cache_value(value)
75
+
76
+ self.push(value)
69
77
  end
78
+ end
79
+
80
+ def pre_build
81
+ self.map!{|obj| obj.to_o}
70
82
 
71
- val.strings_cache.clear
72
- val.names_cache.clear
73
- val.xref_cache.clear
83
+ super
74
84
  end
75
85
 
76
- self[i] = val
77
- i = i + 1
78
- end
79
-
80
- end
81
-
82
- def pre_build
83
- self.map!{|obj| obj.to_o}
84
-
85
- super
86
- end
87
-
88
- def self.parse(stream, parser = nil) #:nodoc:
89
- data = []
90
- offset = stream.pos
91
-
92
- if not stream.skip(@@regexp_open)
93
- raise InvalidArrayObjectError, "No token '#{TOKENS.first}' found"
94
- end
95
-
96
- while stream.skip(@@regexp_close).nil? do
97
-
98
- type = Object.typeof(stream)
99
- if type.nil?
100
- raise InvalidArrayObjectError, "Bad embedded object format"
86
+ def self.parse(stream, parser = nil, hint_type: nil) #:nodoc:
87
+ data = []
88
+ offset = stream.pos
89
+
90
+ if not stream.skip(@@regexp_open)
91
+ raise InvalidArrayObjectError, "No token '#{TOKENS.first}' found"
92
+ end
93
+
94
+ while stream.skip(@@regexp_close).nil? do
95
+ type = Object.typeof(stream)
96
+ raise InvalidArrayObjectError, "Bad embedded object format" if type.nil?
97
+
98
+ value = type.parse(stream, parser)
99
+ data << value
100
+ end
101
+
102
+ array = Array.new(data, parser, hint_type: hint_type)
103
+ array.file_offset = offset
104
+
105
+ array
106
+ end
107
+
108
+ #
109
+ # Converts self into a Ruby array.
110
+ #
111
+ def to_a
112
+ super.map(&:value)
113
+ end
114
+ alias value to_a
115
+
116
+ def to_s #:nodoc:
117
+ content = "#{TOKENS.first} "
118
+ self.each do |entry|
119
+ entry = entry.to_o
120
+
121
+ case entry
122
+ when Dictionary # Do not indent dictionaries inside of arrays.
123
+ content << entry.to_s(indent: 0) << ' '
124
+ else
125
+ content << entry.to_s << ' '
126
+ end
127
+ end
128
+ content << TOKENS.last
129
+
130
+ super(content)
131
+ end
132
+
133
+ def +(other)
134
+ a = Origami::Array.new(self.to_a + other.to_a)
135
+ a.no, a.generation = @no, @generation
136
+
137
+ a
138
+ end
139
+
140
+ def <<(item)
141
+ obj = item.to_o
142
+ obj.parent = self unless obj.indirect?
143
+
144
+ super(obj)
145
+ end
146
+ alias push <<
147
+
148
+ def []=(key,val)
149
+ key, val = key.to_o, val.to_o
150
+ super(key.to_o, val.to_o)
151
+
152
+ val.parent = self unless val.indirect? or val.parent.equal?(self)
153
+
154
+ val
155
+ end
156
+
157
+ def copy
158
+ copy = self.class.new
159
+ self.each do |obj|
160
+ copy << obj.copy
161
+ end
162
+
163
+ copy.parent = @parent
164
+ copy.no, copy.generation = @no, @generation
165
+ copy.set_indirect(true) if self.indirect?
166
+ copy.set_document(@document) if self.indirect?
167
+ copy
168
+ end
169
+
170
+ def cast_to(type, parser = nil)
171
+ super(type)
172
+
173
+ cast = type.new(self, parser)
174
+ cast.parent = self.parent
175
+ cast.no, cast.generation = self.no, self.generation
176
+ if self.indirect?
177
+ cast.set_indirect(true)
178
+ cast.set_document(self.document)
179
+ cast.file_offset = self.file_offset # cast can replace self
180
+ end
181
+
182
+ cast
183
+ end
184
+
185
+ def self.native_type ; Origami::Array end
186
+
187
+ #
188
+ # Parameterized Array class with additional typing information.
189
+ # Example: Array.of(Integer)
190
+ #
191
+ def self.of(klass, *klasses, length: nil)
192
+ Class.new(self) do
193
+ const_set('ARRAY_TYPE', (klasses.empty? and not klass.is_a?(::Array)) ? klass : [ klass ].concat(klasses))
194
+ const_set('STATIC_LENGTH', length)
195
+
196
+ def initialize(data = [], parser = nil)
197
+ super(data, parser, hint_type: self.class.const_get('ARRAY_TYPE'))
198
+ end
199
+
200
+ def pre_build #:nodoc:
201
+ do_type_check if Origami::OPTIONS[:enable_type_checking]
202
+
203
+ super
204
+ end
205
+
206
+ def self.parse(stream, parser = nil)
207
+ super(stream, parser, hint_type: const_get('ARRAY_TYPE'))
208
+ end
209
+
210
+ def do_type_check #:nodoc:
211
+ static_length = self.class.const_get('STATIC_LENGTH')
212
+ array_type = self.class.const_get('ARRAY_TYPE')
213
+
214
+ if static_length and self.length != static_length
215
+ STDERR.puts "Warning: object #{self.class.name} has unexpected length #{self.length} (should be #{static_length})"
216
+ end
217
+
218
+ self.each_with_index do |object, index|
219
+ index_type = array_type.is_a?(::Array) ? array_type[index % array_type.size] : array_type
220
+
221
+ begin
222
+ object_value = object.solve
223
+ rescue InvalidReferenceError
224
+ STDERR.puts "Warning: in object #{self.class}, invalid reference at index #{index}"
225
+ next
226
+ end
227
+
228
+ unless object_value.is_a?(index_type)
229
+ STDERR.puts "Warning: object #{self.class.name || 'Array'} should be composed of #{index_type.name} at index #{index} (got #{object_value.type} instead)"
230
+ end
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ private
237
+
238
+ def cache_value(value)
239
+ case value
240
+ when String then @strings_cache.push(value)
241
+ when Name then @names_cache.push(value)
242
+ when Reference then
243
+ (@xref_cache[value] ||= []).push(self)
244
+ when Dictionary, Array
245
+ @strings_cache.concat(value.strings_cache)
246
+ @names_cache.concat(value.names_cache)
247
+ @xref_cache.update(value.xref_cache) do |_ref, cache1, cache2|
248
+ cache1.concat(cache2)
249
+ end
250
+
251
+ value.strings_cache.clear
252
+ value.names_cache.clear
253
+ value.xref_cache.clear
254
+ end
101
255
  end
102
-
103
- value = type.parse(stream, parser)
104
- data << value
105
- end
106
-
107
- array = Array.new(data)
108
- array.file_offset = offset
109
-
110
- array
111
256
  end
112
-
257
+
113
258
  #
114
- # Converts self into a Ruby array.
259
+ # Class representing a location on a page or a bounding box.
115
260
  #
116
- def to_a
117
- super.map { |item|
118
- item.is_a?(Origami::Object) ? item.value : item
119
- }
120
- end
121
-
122
- def to_s #:nodoc:
123
- content = "#{TOKENS.first} "
124
- self.each { |entry|
125
- content << entry.to_o.to_s + ' '
126
- }
127
- content << TOKENS.last
128
-
129
- super(content)
130
- end
131
-
132
- def +(other)
133
-
134
- a = Origami::Array.new(self.to_a + other.to_a, is_indirect?)
135
- a.no, a.generation = @no, @generation
136
-
137
- return a
138
- end
261
+ class Rectangle < Array.of(Number, length: 4)
139
262
 
140
- def <<(item)
141
- obj = item.to_o
142
- obj.parent = self unless obj.is_indirect?
263
+ def self.[](coords)
264
+ corners =
265
+ if [ :llx, :lly, :urx, :ury ].all? {|p| coords.include?(p)}
266
+ coords.values_at(:llx, :lly, :urx, :ury)
267
+ elsif [ :width, :height ].all? {|p| coords.include?(p)}
268
+ width, height = coords.values_at(:width, :height)
269
+ x = coords.fetch(:x, 0)
270
+ y = coords.fetch(:y, 0)
271
+ [ x, y, x+width, y+height ]
272
+ else
273
+ raise ArgumentError, "Bad arguments for #{self.class}: #{coords.inspect}"
274
+ end
143
275
 
144
- super(obj)
145
- end
146
-
147
- def []=(key,val)
148
- key, val = key.to_o, val.to_o
149
- super(key.to_o,val.to_o)
150
-
151
- val.parent = self unless val.is_indirect? or val.parent.equal?(self)
152
-
153
- val
154
- end
155
-
156
- alias value to_a
157
-
158
- def copy
159
- copy = self.class.new
160
- self.each do |obj|
161
- copy << obj.copy
162
- end
163
-
164
- copy.parent = @parent
165
- copy.no, copy.generation = @no, @generation
166
- copy.set_indirect(true) if is_indirect?
167
- copy.set_pdf(@pdf) if is_indirect?
168
- copy
169
- end
276
+ unless corners.all? { |corner| corner.is_a?(Numeric) }
277
+ raise TypeError, "All coords must be numbers"
278
+ end
170
279
 
171
- def self.native_type ; Origami::Array end
172
-
173
- end
174
-
175
- #
176
- # Class representing a location on a page or a bounding box.
177
- #
178
- class Rectangle < Array
179
-
180
- class << self
181
-
182
- def [](coords)
183
- corners =
184
- if [ :llx, :lly, :urx, :ury ].all? {|p| coords.include?(p)}
185
- coords.values_at(:llx, :lly, :urx, :ury)
186
- elsif [ :width, :height ].all? {|p| coords.include?(p)}
187
- width, height = coords.values_at(:width, :height)
188
- x = coords.values_at(:x).first || 0
189
- y = coords.values_at(:y).first || 0
190
- [ x, y, x+width, y+height ]
191
- else
192
- raise ArgumentError, "Bad arguments for #{self.class}: #{coords.inspect}"
193
- end
194
-
195
- unless corners.all? { |corner| corner.is_a?(Numeric) }
196
- raise TypeError, "All coords must be numbers"
280
+ Rectangle.new(corners)
197
281
  end
198
-
199
- Rectangle.new(*corners)
200
- end
201
-
202
- end
203
-
204
- def initialize(lowerleftx, lowerlefty, upperrightx, upperrighty)
205
- super([ lowerleftx, lowerlefty, upperrightx, upperrighty ])
206
282
  end
207
-
208
- end
209
283
 
210
284
  end