qrest 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.
@@ -0,0 +1,300 @@
1
+ #
2
+ # qrest/modules.rb -- Modules
3
+ #
4
+
5
+ require "qrest/base"
6
+ require "qrest/bitstream"
7
+ require "qrest/demerits"
8
+ require "qrest/bch"
9
+
10
+
11
+ module QRest
12
+
13
+ class Modules
14
+
15
+ POSITIONPATTERNLENGTH = (7 + 1) * 2 + 1
16
+
17
+ ERRORCORRECTLEVEL = { l: 1, m: 0, q: 3, h: 2 }
18
+
19
+ # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable1-e.html
20
+ # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable2-e.html
21
+ # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable3-e.html
22
+ # http://web.archive.org/web/20110710094955/http://www.denso-wave.com/qrcode/vertable4-e.html
23
+ MAXBITS = {
24
+ l: [152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888, 7456, 8048, 8752, 9392, 10_208, 10_960, 11_744, 12_248, 13_048, 13_880, 14_744, 15_640, 16_568, 17_528, 18_448, 19_472, 20_528, 21_616, 22_496, 23_648],
25
+ m: [128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352, 5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10_136, 10_984, 11_640, 12_328, 13_048, 13_800, 14_496, 15_312, 15_936, 16_816, 17_728, 18_672],
26
+ q: [104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880, 4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328],
27
+ h: [72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080, 3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960, 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10_208],
28
+ }
29
+
30
+ MASK_PATTERNS = [
31
+ proc { |i,j| (i + j) % 2 },
32
+ proc { |i,j| i % 2 },
33
+ proc { |i,j| j % 3 },
34
+ proc { |i,j| (i + j) % 3 },
35
+ proc { |i,j| ((i / 2) + (j / 3)) % 2 },
36
+ proc { |i,j| (i * j) % 2 + (i * j) % 3 },
37
+ proc { |i,j| ((i * j) % 2 + (i * j) % 3) % 2 },
38
+ proc { |i,j| ((i * j) % 3 + (i + j) % 2) % 2 },
39
+ ]
40
+
41
+ attr_reader :version, :count, :fields, :demerits
42
+
43
+ class <<self
44
+
45
+ WEIGHTS = {
46
+ same_color: [ 3, 1],
47
+ full_blocks: 3,
48
+ dangerous: 40,
49
+ dark_ratio: 2,
50
+ }
51
+
52
+ def create_best data, version, error_correct_level
53
+ demerits, pattern = nil, 0
54
+ MASK_PATTERNS.length.times { |i|
55
+ test = new data, version, error_correct_level, true, i
56
+ d = test.demerits.total **WEIGHTS
57
+ demerits, pattern = d, i if not demerits or demerits > d
58
+ }
59
+ new data, version, error_correct_level, false, pattern
60
+ end
61
+
62
+ end
63
+
64
+ def initialize data, version, ecl, test, mask_pattern
65
+ count = version * 4 + POSITIONPATTERNLENGTH
66
+ @fields = Array.new count do Array.new count end
67
+ place_position_probe_pattern 0, 0
68
+ place_position_probe_pattern @fields.size - 7, 0
69
+ place_position_probe_pattern 0, @fields.size - 7
70
+ place_position_adjust_pattern version
71
+ place_timing_pattern
72
+ place_format_info test, (ERRORCORRECTLEVEL[ecl]<<3) | mask_pattern
73
+ place_version_info version, test if version >= 7
74
+ bs = BitStream.new data
75
+ walk_fields mask_pattern do bs.get end
76
+ @demerits = Demerits.new @fields if test
77
+ end
78
+
79
+ def inspect
80
+ d = [
81
+ "#{self.class}:",
82
+ (@fields.map { |row| row.map { |x| x.nil? ? "?" : x ? "X" : "." }.join }.join " "),
83
+ ].join " "
84
+ "#<#{d}>"
85
+ end
86
+
87
+ def size ; @fields.size ; end
88
+ def range ; @range ||= 0...size ; end
89
+
90
+ def check? row, col
91
+ range === row or raise Error, "Invalid row: #{row}/#{col}"
92
+ range === col or raise Error, "Invalid column: #{row}/#{col}"
93
+ end
94
+
95
+ def [] row, col ; check? roq, col ; @fields[ row][col] ; end
96
+ def []= row, col, val ; check? row, col ; @fields[ row][col] = val ; end
97
+
98
+ def each_row &block
99
+ @fields.each &block
100
+ end
101
+
102
+ def each_field quiet, start = 0
103
+ ri = start + quiet
104
+ each_row { |row|
105
+ ci = quiet
106
+ row.each { |field|
107
+ yield ri, ci if field
108
+ ci += 1
109
+ }
110
+ ri += 1
111
+ }
112
+ end
113
+
114
+ def each_field_neg quiet, start = 0
115
+ ri = start - quiet
116
+ each_row { |row|
117
+ ci = quiet
118
+ row.each { |field|
119
+ yield ri, ci if field
120
+ ci += 1
121
+ }
122
+ ri -= 1
123
+ }
124
+ end
125
+
126
+ def to_s dark: nil, light: nil, quiet_size: nil
127
+ r = []
128
+ lines dark: dark, light: light, quiet_size: quiet_size do |l| r.push l end
129
+ r.join "\n"
130
+ end
131
+
132
+ def lines dark: nil, light: nil, quiet_size: nil
133
+ dark ||= "X"
134
+ light ||= " "
135
+ quiet_size ||= 0
136
+ qr = light * (@fields.size + 2*quiet_size)
137
+ quiet_size.times do yield qr end
138
+ qc = light * quiet_size
139
+ dl = { true => dark, false =>light, nil => "?"}
140
+ @fields.each do |row|
141
+ yield "" << qc << (row.map do |col| dl[ col] end.join) << qc
142
+ end
143
+ quiet_size.times do yield qr end
144
+ end
145
+
146
+ private
147
+
148
+ R08 = 0..8
149
+ R17 = 1..7
150
+ R35 = 3..5
151
+
152
+ def place_position_probe_pattern row, col
153
+ R08.each do |i|
154
+ r = row + i - 1
155
+ next unless range === r
156
+ iv = R17 === i
157
+ ih = R17.minmax.include? i
158
+ R08.each do |j|
159
+ c = col + j - 1
160
+ next unless range === c
161
+ @fields[ r][ c] = (iv && (R17.minmax.include? j)) ||
162
+ (ih && R17 === j ) ||
163
+ (R35 === i && R35 === j)
164
+ end
165
+ end
166
+ end
167
+
168
+ PATTERN_POSITION_TABLE = [
169
+ [],
170
+ [6, 18],
171
+ [6, 22],
172
+ [6, 26],
173
+ [6, 30],
174
+ [6, 34],
175
+ [6, 22, 38],
176
+ [6, 24, 42],
177
+ [6, 26, 46],
178
+ [6, 28, 50],
179
+ [6, 30, 54],
180
+ [6, 32, 58],
181
+ [6, 34, 62],
182
+ [6, 26, 46, 66],
183
+ [6, 26, 48, 70],
184
+ [6, 26, 50, 74],
185
+ [6, 30, 54, 78],
186
+ [6, 30, 56, 82],
187
+ [6, 30, 58, 86],
188
+ [6, 34, 62, 90],
189
+ [6, 28, 50, 72, 94],
190
+ [6, 26, 50, 74, 98],
191
+ [6, 30, 54, 78, 102],
192
+ [6, 28, 54, 80, 106],
193
+ [6, 32, 58, 84, 110],
194
+ [6, 30, 58, 86, 114],
195
+ [6, 34, 62, 90, 118],
196
+ [6, 26, 50, 74, 98, 122],
197
+ [6, 30, 54, 78, 102, 126],
198
+ [6, 26, 52, 78, 104, 130],
199
+ [6, 30, 56, 82, 108, 134],
200
+ [6, 34, 60, 86, 112, 138],
201
+ [6, 30, 58, 86, 114, 142],
202
+ [6, 34, 62, 90, 118, 146],
203
+ [6, 30, 54, 78, 102, 126, 150],
204
+ [6, 24, 50, 76, 102, 128, 154],
205
+ [6, 28, 54, 80, 106, 132, 158],
206
+ [6, 32, 58, 84, 110, 136, 162],
207
+ [6, 26, 54, 82, 110, 138, 166],
208
+ [6, 30, 58, 86, 114, 142, 170]
209
+ ]
210
+
211
+ MAX_VERSION = PATTERN_POSITION_TABLE.size
212
+
213
+ R_22 = -2..2
214
+
215
+ def place_position_adjust_pattern version
216
+ positions = PATTERN_POSITION_TABLE[ version - 1]
217
+ rd = R_22.minmax
218
+ positions.each do |row|
219
+ positions.each do |col|
220
+ next unless @fields[ row][ col].nil?
221
+ R_22.each do |r|
222
+ pr = row + r
223
+ R_22.each do |c|
224
+ pc = col + c
225
+ @fields[ pr][ pc] = (rd.include? r) || (rd.include? c) || (r == 0 && c == 0)
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ def place_timing_pattern
233
+ (8...@fields.size-8).each do |i|
234
+ @fields[ i][ 6] = @fields[ 6][ i] = i.even?
235
+ end
236
+ end
237
+
238
+ def place_format_info test, ecl
239
+ bits = Bch.format_info ecl
240
+ 15.times do |i|
241
+ mod = !test && bits.odd?
242
+ r =
243
+ case i
244
+ when ...6 then 0
245
+ when ...8 then 1
246
+ else @fields.size - 15
247
+ end
248
+ @fields[ r+i][ 8] = mod
249
+ c =
250
+ case i
251
+ when ...8 then @fields.size
252
+ when ...9 then 16
253
+ else 15
254
+ end
255
+ @fields[ 8][ c - i - 1] = mod
256
+ bits >>= 1
257
+ end
258
+ @fields[ @fields.size - 8][ 8] = !test
259
+ end
260
+
261
+ def place_version_info version, test
262
+ bits = Bch.version version
263
+ 18.times do |i|
264
+ id, im = i.divmod 3
265
+ im += @fields.size - 8 - 3
266
+ @fields[ id][ im] = @fields[ im][ id] = !test && (bits & 1) == 1
267
+ bits >>= 1
268
+ end
269
+ end
270
+
271
+ def walk_fields mask_pattern
272
+ # An image says more than a thousand statements:
273
+ # <https://en.wikipedia.org/wiki/QR_code#Message_placement>
274
+ mp = MASK_PATTERNS[ mask_pattern] or raise ArgumentError, "Bad mask_pattern: #{mask_pattern}"
275
+ row = c2 = @fields.size - 1
276
+ inc = -1
277
+ loop do
278
+ loop do
279
+ 2.times do |c|
280
+ col = c2 - c
281
+ next unless @fields[ row][ col].nil?
282
+ @fields[ row][ col] = (mp.call row, col).zero? ^ yield
283
+ end
284
+ ri = row + inc
285
+ unless range === ri then
286
+ inc = -inc
287
+ break
288
+ end
289
+ row = ri
290
+ end
291
+ break if c2 == 1
292
+ c2 -= 2
293
+ c2 -= 1 if c2 == 6
294
+ end
295
+ end
296
+
297
+ end
298
+
299
+ end
300
+
@@ -0,0 +1,136 @@
1
+ #
2
+ # qrest/polynomial.rb -- Polynomes
3
+ #
4
+
5
+ require "qrest/base"
6
+
7
+
8
+ module QRest
9
+
10
+ class Polynomial
11
+
12
+ @ec = {}
13
+
14
+ class <<self
15
+
16
+ def error_correct error_correct_length
17
+ @ec[ error_correct_length] ||=
18
+ if error_correct_length > 0 then
19
+ e = error_correct_length - 1
20
+ ge = new [1, 0]
21
+ ge.gexp! 1, e
22
+ (error_correct e).multiply ge
23
+ else
24
+ new [1]
25
+ end
26
+ end
27
+
28
+ def zeroes len
29
+ new [ 0] * len
30
+ end
31
+
32
+ end
33
+
34
+ attr_reader :num
35
+
36
+ def initialize num
37
+ num.empty? and raise ArgumentError, "Empty polynomial."
38
+ @num = num
39
+ end
40
+
41
+ def dup ; Polynomial.new @num.dup ; end
42
+
43
+ def [] index ; @num[index] ; end
44
+ def size ; @num.size ; end
45
+ alias length size
46
+
47
+ def norm!
48
+ @num.shift while @num.first == 0
49
+ self
50
+ end
51
+
52
+ def extend! shift
53
+ shift.times { @num.push 0 }
54
+ end
55
+
56
+ def grow! n
57
+ @num.unshift 0 until @num.length >= n
58
+ end
59
+
60
+ def first_glog
61
+ Polynomial.glog @num.first
62
+ end
63
+
64
+ def each_glog
65
+ @num.each_with_index { |n,i|
66
+ yield (Polynomial.glog n), i
67
+ }
68
+ end
69
+
70
+ def gexp! i, n
71
+ @num[ i] ^= Polynomial.gexp n
72
+ end
73
+
74
+ def multiply e
75
+ r = Polynomial.zeroes length + e.length - 1
76
+ each_glog { |gi,i|
77
+ e.each_glog { |gj,j|
78
+ r.gexp! i + j, gi + gj
79
+ }
80
+ }
81
+ r
82
+ end
83
+
84
+ def error_mod error_count
85
+ e = Polynomial.error_correct error_count
86
+ ef = e.first_glog
87
+ p = dup
88
+ p.extend! error_count
89
+ loop do
90
+ p.norm!
91
+ break if p.length < e.length
92
+ ratio = p.first_glog - ef
93
+ e.each_glog { |gi,i|
94
+ p.gexp! i, ratio + gi
95
+ }
96
+ end
97
+ p.grow! error_count
98
+ p
99
+ end
100
+
101
+
102
+ EXP_TABLE = []
103
+ (0...8).each do |i|
104
+ EXP_TABLE.push 1 << i
105
+ end
106
+ (8...256).each do |i|
107
+ EXP_TABLE.push EXP_TABLE[i-4] ^ EXP_TABLE[i-5] ^ EXP_TABLE[i-6] ^ EXP_TABLE[i-8]
108
+ end
109
+
110
+ LOG_TABLE = [nil] * 256
111
+ 255.times do |i|
112
+ LOG_TABLE[EXP_TABLE[i]] = i
113
+ end
114
+
115
+ EXP_TABLE.freeze
116
+ LOG_TABLE.freeze
117
+
118
+ class <<self
119
+
120
+ def glog n
121
+ n >= 1 or raise Error, "Internal error: glog(#{n})."
122
+ LOG_TABLE[n]
123
+ end
124
+
125
+ def gexp n
126
+ while n < 0 do n += 255 end
127
+ while n >= 256 do n -= 255 end
128
+ EXP_TABLE[n]
129
+ end
130
+
131
+ end
132
+
133
+ end
134
+
135
+ end
136
+
@@ -0,0 +1,143 @@
1
+ #
2
+ # qrest/rsblocks.rb -- Reed-Solomon-Code blocks
3
+ #
4
+
5
+ require "qrest/base"
6
+ require "qrest/bitbuffer"
7
+ require "qrest/polynomial"
8
+
9
+
10
+ module QRest
11
+
12
+ class RSBlocks
13
+
14
+ class Block
15
+
16
+ attr_reader :data_count, :total_count
17
+
18
+ def initialize total_count, data_count
19
+ @total_count, @data_count = total_count, data_count
20
+ end
21
+
22
+ def error_count ; total_count - data_count ; end
23
+
24
+ end
25
+
26
+ def initialize version, bs
27
+ @version = version
28
+ @list = bs.map { |num,total,data| [ num, (Block.new total, data)] }
29
+ end
30
+
31
+ def each ; @list.each { |(n,b)| n.times { yield b } } ; end
32
+ def sum sym ; @list.map { |(n,b)| n * (b.send sym) }.sum ; end
33
+ def max sym ; @list.map { |(_,b)| (b.send sym) }.max ; end
34
+
35
+ def create_data data
36
+ l = sum :data_count
37
+ b = BitBuffer.build l do |buf| data.write buf, @version end
38
+ create_bytes b
39
+ end
40
+
41
+ private
42
+
43
+ def create_bytes buffer
44
+ dcdata, ecdata = [], []
45
+ i = 0
46
+ each do |rs|
47
+ p = Polynomial.new buffer[ i, rs.data_count]
48
+ i += rs.data_count
49
+ dcdata.push p.num
50
+ ecdata.push (p.error_mod rs.error_count).num
51
+ end
52
+ data = []
53
+ (max :data_count).times do |j|
54
+ dcdata.each do |dc|
55
+ data.push dc[j] if j < dc.size
56
+ end
57
+ end
58
+ (max :error_count).times do |j|
59
+ ecdata.each do |ec|
60
+ data.push ec[j] if j < ec.size
61
+ end
62
+ end
63
+ data.length == (sum :total_count) or
64
+ raise Error, "Internal error. Please consider a report."
65
+ data
66
+ end
67
+
68
+ @blocks = Hash.new { |h,k|
69
+ v = nil
70
+ RS_BLOCK_TABLE.each_line { |l|
71
+ n, l = l.split nil, 2
72
+ if n.to_i == k then
73
+ v = {}
74
+ while l =~ /([a-z])\b([ 0-9,]+)/ do
75
+ n, b, l = $1, $2, $'
76
+ b.rstrip!
77
+ v[ n.to_sym] = (b.split /,/).map { |t| t.split.map { |i| i.to_i } }
78
+ end
79
+ break
80
+ end
81
+ }
82
+ h[k] = v
83
+ }
84
+
85
+ class <<self
86
+
87
+ private :new
88
+
89
+ def get version, error_correct_level
90
+ v = @blocks[version] or raise ArgumentError, "No RS block for version #{version}"
91
+ new version, v[error_correct_level]
92
+ end
93
+
94
+ end
95
+
96
+ # http://www.thonky.com/qr-code-tutorial/error-correction-table/
97
+ RS_BLOCK_TABLE = <<~EOT
98
+ 1 l 1 26 19, m 1 26 16, q 1 26 13, h 1 26 9,
99
+ 2 l 1 44 34, m 1 44 28, q 1 44 22, h 1 44 16,
100
+ 3 l 1 70 55, m 1 70 44, q 2 35 17, h 2 35 13,
101
+ 4 l 1 100 80, m 2 50 32, q 2 50 24, h 4 25 9,
102
+ 5 l 1 134 108, m 2 67 43, q 2 33 15, 2 34 16, h 2 33 11, 2 34 12,
103
+ 6 l 2 86 68, m 4 43 27, q 4 43 19, h 4 43 15,
104
+ 7 l 2 98 78, m 4 49 31, q 2 32 14, 4 33 15, h 4 39 13, 1 40 14,
105
+ 8 l 2 121 97, m 2 60 38, 2 61 39, q 4 40 18, 2 41 19, h 4 40 14, 2 41 15,
106
+ 9 l 2 146 116, m 3 58 36, 2 59 37, q 4 36 16, 4 37 17, h 4 36 12, 4 37 13,
107
+ 10 l 2 86 68, 2 87 69, m 4 69 43, 1 70 44, q 6 43 19, 2 44 20, h 6 43 15, 2 44 16,
108
+ 11 l 4 101 81, m 1 80 50, 4 81 51, q 4 50 22, 4 51 23, h 3 36 12, 8 37 13,
109
+ 12 l 2 116 92, 2 117 93, m 6 58 36, 2 59 37, q 4 46 20, 6 47 21, h 7 42 14, 4 43 15,
110
+ 13 l 4 133 107, m 8 59 37, 1 60 38, q 8 44 20, 4 45 21, h 12 33 11, 4 34 12,
111
+ 14 l 3 145 115, 1 146 116, m 4 64 40, 5 65 41, q 11 36 16, 5 37 17, h 11 36 12, 5 37 13,
112
+ 15 l 5 109 87, 1 110 88, m 5 65 41, 5 66 42, q 5 54 24, 7 55 25, h 11 36 12, 7 37 13,
113
+ 16 l 5 122 98, 1 123 99, m 7 73 45, 3 74 46, q 15 43 19, 2 44 20, h 3 45 15, 13 46 16,
114
+ 17 l 1 135 107, 5 136 108, m 10 74 46, 1 75 47, q 1 50 22, 15 51 23, h 2 42 14, 17 43 15,
115
+ 18 l 5 150 120, 1 151 121, m 9 69 43, 4 70 44, q 17 50 22, 1 51 23, h 2 42 14, 19 43 15,
116
+ 19 l 3 141 113, 4 142 114, m 3 70 44, 11 71 45, q 17 47 21, 4 48 22, h 9 39 13, 16 40 14,
117
+ 20 l 3 135 107, 5 136 108, m 3 67 41, 13 68 42, q 15 54 24, 5 55 25, h 15 43 15, 10 44 16,
118
+ 21 l 4 144 116, 4 145 117, m 17 68 42, q 17 50 22, 6 51 23, h 19 46 16, 6 47 17,
119
+ 22 l 2 139 111, 7 140 112, m 17 74 46, q 7 54 24, 16 55 25, h 34 37 13,
120
+ 23 l 4 151 121, 5 152 122, m 4 75 47, 14 76 48, q 11 54 24, 14 55 25, h 16 45 15, 14 46 16,
121
+ 24 l 6 147 117, 4 148 118, m 6 73 45, 14 74 46, q 11 54 24, 16 55 25, h 30 46 16, 2 47 17,
122
+ 25 l 8 132 106, 4 133 107, m 8 75 47, 13 76 48, q 7 54 24, 22 55 25, h 22 45 15, 13 46 16,
123
+ 26 l 10 142 114, 2 143 115, m 19 74 46, 4 75 47, q 28 50 22, 6 51 23, h 33 46 16, 4 47 17,
124
+ 27 l 8 152 122, 4 153 123, m 22 73 45, 3 74 46, q 8 53 23, 26 54 24, h 12 45 15, 28 46 16,
125
+ 28 l 3 147 117, 10 148 118, m 3 73 45, 23 74 46, q 4 54 24, 31 55 25, h 11 45 15, 31 46 16,
126
+ 29 l 7 146 116, 7 147 117, m 21 73 45, 7 74 46, q 1 53 23, 37 54 24, h 19 45 15, 26 46 16,
127
+ 30 l 5 145 115, 10 146 116, m 19 75 47, 10 76 48, q 15 54 24, 25 55 25, h 23 45 15, 25 46 16,
128
+ 31 l 13 145 115, 3 146 116, m 2 74 46, 29 75 47, q 42 54 24, 1 55 25, h 23 45 15, 28 46 16,
129
+ 32 l 17 145 115, m 10 74 46, 23 75 47, q 10 54 24, 35 55 25, h 19 45 15, 35 46 16,
130
+ 33 l 17 145 115, 1 146 116, m 14 74 46, 21 75 47, q 29 54 24, 19 55 25, h 11 45 15, 46 46 16,
131
+ 34 l 13 145 115, 6 146 116, m 14 74 46, 23 75 47, q 44 54 24, 7 55 25, h 59 46 16, 1 47 17,
132
+ 35 l 12 151 121, 7 152 122, m 12 75 47, 26 76 48, q 39 54 24, 14 55 25, h 22 45 15, 41 46 16,
133
+ 36 l 6 151 121, 14 152 122, m 6 75 47, 34 76 48, q 46 54 24, 10 55 25, h 2 45 15, 64 46 16,
134
+ 37 l 17 152 122, 4 153 123, m 29 74 46, 14 75 47, q 49 54 24, 10 55 25, h 24 45 15, 46 46 16,
135
+ 38 l 4 152 122, 18 153 123, m 13 74 46, 32 75 47, q 48 54 24, 14 55 25, h 42 45 15, 32 46 16,
136
+ 39 l 20 147 117, 4 148 118, m 40 75 47, 7 76 48, q 43 54 24, 22 55 25, h 10 45 15, 67 46 16,
137
+ 40 l 19 148 118, 6 149 119, m 18 75 47, 31 76 48, q 34 54 24, 34 55 25, h 20 45 15, 61 46 16,
138
+ EOT
139
+
140
+ end
141
+
142
+ end
143
+