qrtools 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.
Files changed (66) hide show
  1. data/History.txt +6 -0
  2. data/Manifest.txt +65 -0
  3. data/README.txt +75 -0
  4. data/Rakefile +49 -0
  5. data/bin/qrdecode +14 -0
  6. data/ext/qrtools/Makefile.in +65 -0
  7. data/ext/qrtools/bitstream.cpp +147 -0
  8. data/ext/qrtools/bitstream.h +48 -0
  9. data/ext/qrtools/codedata.cpp +506 -0
  10. data/ext/qrtools/codedata.h +95 -0
  11. data/ext/qrtools/container.cpp +288 -0
  12. data/ext/qrtools/container.h +175 -0
  13. data/ext/qrtools/decodeqr.h +286 -0
  14. data/ext/qrtools/ecidecoder.cpp +341 -0
  15. data/ext/qrtools/ecidecoder.h +110 -0
  16. data/ext/qrtools/extconf.rb +24 -0
  17. data/ext/qrtools/formatinfo.cpp +195 -0
  18. data/ext/qrtools/formatinfo.h +113 -0
  19. data/ext/qrtools/galois.cpp +593 -0
  20. data/ext/qrtools/galois.h +134 -0
  21. data/ext/qrtools/imagereader.cpp +1099 -0
  22. data/ext/qrtools/imagereader.h +127 -0
  23. data/ext/qrtools/libdecodeqr.cpp +195 -0
  24. data/ext/qrtools/libdecodeqr.dep +80 -0
  25. data/ext/qrtools/libdecodeqr.dsp +160 -0
  26. data/ext/qrtools/libdecodeqr.dsw +29 -0
  27. data/ext/qrtools/libdecodeqr.mak +245 -0
  28. data/ext/qrtools/qrerror.h +40 -0
  29. data/ext/qrtools/qrtools.c +17 -0
  30. data/ext/qrtools/qrtools.h +21 -0
  31. data/ext/qrtools/qrtools_decoder.c +123 -0
  32. data/ext/qrtools/qrtools_decoder.h +10 -0
  33. data/ext/qrtools/qrtools_encoder.c +63 -0
  34. data/ext/qrtools/qrtools_encoder.h +10 -0
  35. data/ext/qrtools/qrtools_header.c +51 -0
  36. data/ext/qrtools/qrtools_header.h +10 -0
  37. data/ext/qrtools/qrtools_image.c +80 -0
  38. data/ext/qrtools/qrtools_image.h +11 -0
  39. data/ext/qrtools/qrtools_qrcode.c +36 -0
  40. data/ext/qrtools/qrtools_qrcode.h +10 -0
  41. data/ext/qrtools/qrtools_ui_camera.c +58 -0
  42. data/ext/qrtools/qrtools_ui_camera.h +10 -0
  43. data/ext/qrtools/qrtools_ui_window.c +40 -0
  44. data/ext/qrtools/qrtools_ui_window.h +10 -0
  45. data/ext/qrtools/qrtypes.h +42 -0
  46. data/ext/qrtools/version.h +42 -0
  47. data/lib/qrtools.rb +11 -0
  48. data/lib/qrtools/decoder.rb +17 -0
  49. data/lib/qrtools/encoder.rb +14 -0
  50. data/lib/qrtools/image.rb +43 -0
  51. data/lib/qrtools/qrcode.rb +54 -0
  52. data/lib/qrtools/ui/camera.rb +28 -0
  53. data/lib/qrtools/ui/window.rb +16 -0
  54. data/lib/qrtools/version.rb +3 -0
  55. data/qrtools.gemspec +38 -0
  56. data/test/assets/01-1.jpg +0 -0
  57. data/test/helper.rb +17 -0
  58. data/test/test_decoder.rb +67 -0
  59. data/test/test_encoder.rb +35 -0
  60. data/test/test_header.rb +14 -0
  61. data/test/test_image.rb +19 -0
  62. data/test/test_qrcode.rb +78 -0
  63. data/test/test_qrdecode.rb +0 -0
  64. data/test/ui/test_camera.rb +43 -0
  65. data/test/ui/test_window.rb +34 -0
  66. metadata +138 -0
@@ -0,0 +1,506 @@
1
+ /////////////////////////////////////////////////////////////////////////
2
+ //
3
+ // codedata.cpp --a part of libdecodeqr
4
+ //
5
+ // Copyright(C) 2007 NISHI Takao <zophos@koka-in.org>
6
+ // JMA (Japan Medical Association)
7
+ // NaCl (Network Applied Communication Laboratory Ltd.)
8
+ //
9
+ // This is free software with ABSOLUTELY NO WARRANTY.
10
+ // You can redistribute and/or modify it under the terms of LGPL.
11
+ //
12
+ // $Id: codedata.cpp 36 2007-02-21 23:22:03Z zophos $
13
+ //
14
+ #include "codedata.h"
15
+
16
+ namespace Qr{
17
+
18
+ /////////////////////////////////////////////////////////////////////////
19
+ //
20
+ // RS block structure table
21
+ // ( from JISX0510(2004) 8.5.1 Table.12-18 pp.30-36)
22
+ //
23
+ // [version][level]={
24
+ // Tw: total words,
25
+ // Sb: number of smaller RS blocks,
26
+ // Tcs: total codes in each smaller RS block,
27
+ // Dcs: data codes in each smaller RS block,
28
+ // Ec: error correctable words in each block
29
+ // }
30
+ //
31
+ // level M: 0
32
+ // L: 1
33
+ // H: 2
34
+ // Q: 3
35
+ //
36
+ // Tcl: total codes in each largerer RS block = Tcs + 1
37
+ // Dcs: data codes in each larger RS block = Dcs + 1
38
+ // Lb: Number of larger RS blocks = (Tw - Sb * Tcs) / Tcl
39
+ // Tb: Total RS Blocks = Sb + Lb
40
+ //
41
+ const static int RS_BLOCK_ALIGN[41][4][5]={
42
+ // version 0 (not exist, just a dummy)
43
+ {{0,0,0,0,0},{0,0,0,0,0},
44
+ {0,0,0,0,0},{0,0,0,0,0}},
45
+
46
+ // version 1
47
+ {{26,1,26,16,4},{26,1,26,19,2},
48
+ {26,1,26,9,8},{26,1,26,13,6}},
49
+ {{44,1,44,28,8},{44,1,44,34,4},
50
+ {44,1,44,16,14},{44,1,44,22,11}},
51
+ {{70,1,70,44,13},{70,1,70,55,7},
52
+ {70,2,35,13,11},{70,2,35,17,9}},
53
+ {{100,2,50,32,9},{100,1,100,80,10},
54
+ {100,4,25,9,8},{100,2,50,24,13}},
55
+
56
+ {{134,2,67,43,12},{134,1,134,108,13},
57
+ {134,2,33,11,11},{134,2,33,15,9}},
58
+ {{172,4,43,27,8},{172,2,86,68,9},
59
+ {172,4,43,15,14},{172,4,43,19,12}},
60
+ {{196,4,49,31,9},{196,2,98,78,10},
61
+ {196,4,39,13,13},{196,2,32,14,9}},
62
+ {{242,2,60,38,11},{242,2,121,97,12},
63
+ {242,4,40,14,13},{242,4,40,18,11}},
64
+ {{292,3,58,36,11},{292,2,146,116,15},
65
+ {292,4,36,12,12},{292,4,36,16,10}},
66
+
67
+ // version 10
68
+ {{346,4,69,43,13},{346,2,86,68,9},
69
+ {346,6,43,15,14},{346,6,43,19,12}},
70
+ {{404,1,80,50,15},{404,4,101,81,10},
71
+ {404,3,36,12,12},{404,4,50,22,14}},
72
+ {{466,6,58,36,11},{466,2,116,92,12},
73
+ {466,7,42,14,14},{466,4,46,20,14}},
74
+ {{532,8,59,37,11},{532,4,133,107,13},
75
+ {532,12,33,11,11},{532,8,44,20,12}},
76
+ {{581,5,65,41,12},{581,5,109,87,11},
77
+ {581,11,36,12,12},{581,5,54,24,15}},
78
+
79
+ {{655,5,65,41,12},{655,5,109,87,11},
80
+ {655,11,36,12,12},{655,5,54,24,15}},
81
+ {{733,7,73,45,14},{733,5,122,98,12},
82
+ {733,3,45,15,15},{733,15,43,19,12}},
83
+ {{815,10,74,46,14},{815,1,135,107,14},
84
+ {815,2,42,14,14},{815,1,50,22,14}},
85
+ {{901,9,69,43,13},{901,5,150,120,15},
86
+ {901,2,42,14,14},{901,17,50,22,14}},
87
+ {{991,3,70,44,13},{991,3,141,113,14},
88
+ {991,9,39,13,13},{991,17,47,21,13}},
89
+
90
+ // version 20
91
+ {{1085,3,67,41,13},{1085,3,135,107,14},
92
+ {1085,15,43,15,14},{1085,15,54,24,15}},
93
+ {{1156,17,68,42,13},{1156,4,144,116,14},
94
+ {1156,19,46,16,15},{1156,17,50,22,14}},
95
+ {{1258,17,74,46,14},{1258,2,139,111,14},
96
+ {1258,34,37,13,12},{1258,7,54,24,15}},
97
+ {{1364,4,75,47,14},{1364,4,151,121,15},
98
+ {1364,16,45,15,15},{1364,11,54,24,15}},
99
+ {{1474,6,73,45,14},{1474,6,147,117,15},
100
+ {1474,30,46,16,15},{1474,11,54,24,15}},
101
+
102
+ {{1588,8,75,47,14},{1588,8,132,106,13},
103
+ {1588,22,45,15,15},{1588,7,54,24,15}},
104
+ {{1706,19,74,46,14},{1706,10,142,114,14},
105
+ {1706,33,46,16,15},{1706,28,50,22,14}},
106
+ {{1828,22,73,45,14},{1828,8,152,122,15},
107
+ {1828,12,45,15,15},{1828,8,53,23,15}},
108
+ {{1921,3,73,45,14},{1921,3,147,117,15},
109
+ {1921,11,45,15,15},{1921,4,54,24,15}},
110
+ {{2051,21,73,45,14},{2051,7,146,116,15},
111
+ {2051,19,45,15,15},{2051,21,73,45,14}},
112
+
113
+ // version 30
114
+ {{2185,19,75,47,14},{2185,5,145,115,15},
115
+ {2185,23,45,15,15},{2185,15,54,24,15}},
116
+ {{2323,2,74,46,14},{2323,13,145,115,15},
117
+ {2323,23,45,15,15},{2323,42,54,24,15}},
118
+ {{2465,10,74,46,14},{2465,17,145,115,15},
119
+ {2465,19,45,15,15},{2465,10,54,24,15}},
120
+ {{2611,14,74,46,14},{2611,17,145,115,15},
121
+ {2611,11,45,15,15},{2611,29,54,24,15}},
122
+ {{2761,14,74,46,14},{2761,13,145,115,15},
123
+ {2761,46,16,15,},{2761,44,54,24,15}},
124
+
125
+ {{2876,12,75,47,14},{2876,12,151,121,15},
126
+ {2876,22,45,15,15},{2876,39,54,24,15}},
127
+ {{3034,6,75,47,14},{3034,6,151,121,15},
128
+ {3034,2,45,15,15},{3034,46,54,24,15}},
129
+ {{3196,29,74,46,14},{3196,17,152,122,15},
130
+ {3196,24,45,15,15},{3196,49,54,24,15}},
131
+ {{3362,13,74,46,14},{3362,4,152,122,15},
132
+ {3362,42,45,15,15},{3362,48,54,24,15}},
133
+ {{3532,40,75,47,14},{3532,20,147,117,15},
134
+ {3532,10,45,15,15},{3532,43,54,24,15}},
135
+
136
+ // version 40
137
+ {{3706,18,75,47,14},{3706,19,148,118,15},
138
+ {3706,20,45,15,15},{3706,34,54,24,15}}
139
+ };
140
+
141
+ /////////////////////////////////////////////////////////////////////////
142
+ //
143
+ //
144
+ //
145
+ CodeBlock::CodeBlock(int total_words,int data_words,int capability,
146
+ Galois::Field *gf)
147
+ {
148
+ this->total_words=total_words;
149
+ this->data_words=data_words;
150
+ this->capability=capability;
151
+ this->_gf=gf;
152
+
153
+ this->data=new unsigned char[this->total_words];
154
+ this->clear();
155
+ }
156
+ CodeBlock::~CodeBlock()
157
+ {
158
+ delete this->data;
159
+ }
160
+
161
+ void CodeBlock::clear()
162
+ {
163
+ memset(this->data,0,this->total_words);
164
+ this->_size=0;
165
+ }
166
+
167
+ unsigned char *CodeBlock::push(unsigned char data)
168
+ {
169
+ if(this->_size>=this->total_words)
170
+ return(NULL);
171
+
172
+ this->data[this->_size]=data;
173
+ this->_size++;
174
+ return(&(this->data[this->_size-1]));
175
+ }
176
+ bool CodeBlock::has_vacant_data()
177
+ {
178
+ return(this->_size<this->data_words);
179
+ }
180
+
181
+ int CodeBlock::error_correct()
182
+ {
183
+ Galois::BCH *bch=new Galois::BCH(this->_gf,
184
+ this->total_words,
185
+ this->capability);
186
+
187
+ int i,j;
188
+ for(i=this->total_words-1,j=0;i>=0;i--,j++){
189
+ bch->set(j,this->_gf->vect2nomial(this->data[i]));
190
+ }
191
+ int errors=bch->decode();
192
+ if(errors<=0){
193
+ delete bch;
194
+ return(errors);
195
+ }
196
+
197
+
198
+ //
199
+ // get each error size
200
+ //
201
+ //
202
+ // JISX0510:2004 Appendix B (p.64) step c) makes us mistake.
203
+ //
204
+ // s(n): n.th digit of sent data on GF(2^8)
205
+ // r(n): n.th digit of received data on GF(2^8)
206
+ // e(n): n.th error data on GF(2^8)
207
+ //
208
+ // If there was 3 errors on 1st ,2nd ,and n.th digit, received
209
+ // data R can be express shown as below;
210
+ //
211
+ // R = ( r(0), r(1), r(2), ... r(n) )
212
+ // = ( s(0), s(1)+e(0), s(2)+e(1), ... s(n)+e(2) )
213
+ //
214
+ // Error syndromes ES(n) are;
215
+ //
216
+ // ES(0) = sum{r(i) * a(0*i)}
217
+ // = sum{s(i)} +e(0) +e(1) +e(2)
218
+ // = e(0) +e(1) +e(2)
219
+ // ES(1) = sum{r(i)*a(1*i)}
220
+ // = sum{s(i)*a(i)} +a(1)*e(0) +a(2)*e(1) +a(n)*e(2)
221
+ // = a(1)*e(0) +a(2)*e(1) +a(n)*e(2)
222
+ // :
223
+ //
224
+ // ES(x) = sum{r(i)*a(x*i)}
225
+ // = sum{s(i)*a(x*i)} +a(x)*e(0) +a(2*x)*e(1) +a(n*x)*e(2)
226
+ // = a(x)*e(0) +a(2*x)*e(1) +a(n*x)*e(2)
227
+ //
228
+ // where a(x) is primitive of GF(2^8)
229
+ //
230
+ // So, each error size e(n) can be taken following equatation;
231
+ //
232
+ // e(0) +e(1) +e(2) =ES(0)
233
+ // a(1)*e(0) +a(2)*e(1) +a(n)*e(2) =ES(1)
234
+ // a(2)*e(0) +a(4)*e(1) +a(2*n)*e(2) =ES(2)
235
+ //
236
+ Galois::Polynomial *mat=new Galois::Polynomial(errors,
237
+ errors+1);
238
+ for(j=0;j<errors;j++){
239
+ for(i=0;i<errors;i++){
240
+ mat->set(j,i,this->_gf->exp2nomial(j*bch->error_pos[i]));
241
+ }
242
+ mat->set(j,i,bch->syndromes[j]);
243
+ }
244
+ Galois::Polynomial *err=mat->solve();
245
+
246
+ if(err){
247
+ //
248
+ // correct received data
249
+ //
250
+ //
251
+ // s(n) = r(n) + e(n)
252
+ //
253
+ // s(n): n.th digit of send data on GF(2^8)
254
+ // r(n): n.th digit of recieved data on GF(2^8)
255
+ // e(n): error size of n.th digit on GF(2^8)
256
+ //
257
+ for(i=0;i<errors;i++){
258
+ int p=this->total_words-bch->error_pos[i]-1;
259
+ this->data[p]=(*this->_gf->vect2nomial(this->data[p])+
260
+ *err->get(i)).to_vect();
261
+ }
262
+ delete err;
263
+ }
264
+ else{
265
+ errors=-1;
266
+ }
267
+
268
+ delete mat;
269
+ delete bch;
270
+
271
+ return(errors);
272
+ }
273
+
274
+
275
+ /////////////////////////////////////////////////////////////////////////
276
+ //
277
+ //
278
+ //
279
+ CodeData::CodeData(int version,int level)
280
+ {
281
+ this->_gf=new Galois::Field(8);
282
+
283
+ this->version=version;
284
+ this->level=level;
285
+
286
+ this->total_words=RS_BLOCK_ALIGN[version][level][0];
287
+ int larger_block_words=RS_BLOCK_ALIGN[version][level][2]+1;
288
+ int larger_block_datas=RS_BLOCK_ALIGN[version][level][3]+1;
289
+ int larger_blocks=(this->total_words-
290
+ RS_BLOCK_ALIGN[version][level][1]*
291
+ RS_BLOCK_ALIGN[version][level][2]
292
+ )/larger_block_words;
293
+
294
+ this->data_blocks=RS_BLOCK_ALIGN[version][level][1]+larger_blocks;
295
+
296
+ this->data_words=RS_BLOCK_ALIGN[version][level][1]*
297
+ RS_BLOCK_ALIGN[version][level][3]+
298
+ larger_blocks*larger_block_datas;
299
+
300
+ this->data=new CodeBlock *[this->data_blocks];
301
+
302
+ int i;
303
+ for(i=0;i<RS_BLOCK_ALIGN[version][level][1];i++){
304
+ this->data[i]=new CodeBlock(
305
+ RS_BLOCK_ALIGN[version][level][2],
306
+ RS_BLOCK_ALIGN[version][level][3],
307
+ RS_BLOCK_ALIGN[version][level][4],
308
+ this->_gf);
309
+ }
310
+ for(i=RS_BLOCK_ALIGN[version][level][1];i<this->data_blocks;i++){
311
+ this->data[i]=new CodeBlock(
312
+ larger_block_words,
313
+ larger_block_datas,
314
+ RS_BLOCK_ALIGN[version][level][4],
315
+ this->_gf);
316
+ }
317
+
318
+ this->length=0;
319
+ this->byte_length=0;
320
+ this->_raw_data=NULL;
321
+
322
+
323
+ this->_size=0;
324
+ this->_index=0;
325
+
326
+ this->status=0;
327
+ }
328
+ CodeData::~CodeData()
329
+ {
330
+ if(this->_raw_data)
331
+ delete this->_raw_data;
332
+
333
+ for(int i=0;i<this->data_blocks;i++){
334
+ delete this->data[i];
335
+ }
336
+ delete this->data;
337
+ delete this->_gf;
338
+ }
339
+
340
+ void CodeData::clear()
341
+ {
342
+ for(int i=0;i<this->data_blocks;i++){
343
+ this->data[i]->clear();
344
+ }
345
+
346
+ this->length=0;
347
+ this->byte_length=0;
348
+ if(this->_raw_data){
349
+ delete this->_raw_data;
350
+ this->_raw_data=NULL;
351
+ }
352
+ this->_size=0;
353
+ this->_index=0;
354
+
355
+ this->status=0;
356
+ }
357
+
358
+ unsigned char *CodeData::push(unsigned char data)
359
+ {
360
+ if(this->_size<this->data_words){
361
+ for(int i=0;i<this->data_blocks;i++){
362
+ this->_index=(this->_index+i)%this->data_blocks;
363
+ if(this->data[this->_index]->has_vacant_data()){
364
+ unsigned char *ret=this->data[this->_index]->push(data);
365
+ this->_size++;
366
+ this->_index++;
367
+ return(ret);
368
+ }
369
+ }
370
+ throw;
371
+ }
372
+ else{
373
+ this->_index=this->_index%this->data_blocks;
374
+ unsigned char *ret=this->data[this->_index]->push(data);
375
+ this->_size++;
376
+ this->_index++;
377
+ return(ret);
378
+ }
379
+ }
380
+
381
+ unsigned char *CodeData::dump()
382
+ {
383
+ unsigned char *buf=new unsigned char[this->total_words];
384
+ for(int i=0,j=0;i<data_blocks;i++){
385
+ memcpy(buf+j,this->data[i]->data,
386
+ this->data[i]->total_words);
387
+ j+=this->data[i]->total_words;
388
+ }
389
+
390
+ return(buf);
391
+ }
392
+ unsigned char *CodeData::dump_block(int index)
393
+ {
394
+ unsigned char *buf=new unsigned char[this->data[index]->total_words];
395
+ memcpy(buf,this->data[index]->data,
396
+ this->data[index]->total_words);
397
+
398
+ return(buf);
399
+ }
400
+ unsigned char *CodeData::dump_data()
401
+ {
402
+ unsigned char *buf=new unsigned char[this->data_words];
403
+ for(int i=0,j=0;i<data_blocks;i++){
404
+ memcpy(buf+j,this->data[i]->data,
405
+ this->data[i]->data_words);
406
+ j+=this->data[i]->data_words;
407
+ }
408
+
409
+ return(buf);
410
+ }
411
+
412
+ unsigned char *CodeData::raw_data()
413
+ {
414
+ return(this->_raw_data);
415
+ }
416
+
417
+ int CodeData::decode()
418
+ {
419
+ if(this->_raw_data)
420
+ return(0);
421
+
422
+ int ret=this->_error_correct();
423
+ if(ret<0)
424
+ this->status|=QR_CODEDATA_UNRECOVERABLE;
425
+
426
+ unsigned char *tmp=this->dump_data();
427
+ BitStream *bitstream=new BitStream(tmp,this->data_words);
428
+ delete tmp;
429
+
430
+ unsigned char mode=0;
431
+ Qr::ECI::Decoder *decoder=NULL;
432
+ do{
433
+ bitstream->read(&mode,sizeof(mode),4);
434
+ switch(mode){
435
+ case 0: // end of data
436
+ decoder=NULL;
437
+ break;
438
+ case 1:
439
+ decoder=new Qr::ECI::NumericalDecoder();
440
+ break;
441
+ case 2: // arabic and numeric
442
+ decoder=new Qr::ECI::AlphabeticalDecoder();
443
+ break;
444
+ case 4: // 8-bit byte
445
+ decoder=new Qr::ECI::ByteDecoder();
446
+ break;
447
+ case 8: // kanji
448
+ decoder=new Qr::ECI::KanjiDecoder();
449
+ break;
450
+ case 7: // ECI
451
+ case 3: // joint
452
+ case 5: // FNC1 1st
453
+ case 9: // FNC1 2nd
454
+ default:
455
+ this->status|=QR_CODEDATA_NOT_SUPPORT_ECI;
456
+ decoder=NULL;
457
+ }
458
+
459
+ if(!decoder)
460
+ break;
461
+
462
+ int ret=decoder->decode(this->version,bitstream);
463
+ if(ret){
464
+ if(this->_raw_data){
465
+ unsigned char *buf=this->_raw_data;
466
+ int sz=this->byte_length+decoder->byte_length;
467
+ this->_raw_data=new unsigned char[sz];
468
+ memcpy(this->_raw_data,buf,this->byte_length);
469
+ delete buf;
470
+ memcpy(this->_raw_data+this->byte_length,
471
+ decoder->raw_data(),
472
+ decoder->byte_length);
473
+ }
474
+ else{
475
+ this->_raw_data=new unsigned char[decoder->byte_length];
476
+ memcpy(this->_raw_data,
477
+ decoder->raw_data(),
478
+ decoder->byte_length);
479
+ }
480
+ this->length+=decoder->length;
481
+ this->byte_length+=decoder->byte_length;
482
+
483
+ delete decoder;
484
+ }
485
+ }while(!bitstream->is_eod());
486
+
487
+ delete bitstream;
488
+
489
+ return(ret);
490
+ }
491
+
492
+ int CodeData::_error_correct()
493
+ {
494
+ int ret=0;
495
+ for(int i=0;i<this->data_blocks;i++){
496
+ int c=this->data[i]->error_correct();
497
+ if(ret<0||c<0){
498
+ ret=-1;
499
+ }
500
+ else
501
+ ret+=c;
502
+ }
503
+
504
+ return(ret);
505
+ }
506
+ }