1 /* maxicode.c - Handles Maxicode */
2
3 /*
4 libzint - the open source barcode library
5 Copyright (C) 2010-2017 Robin Stuart <rstuart114@gmail.com>
6
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
10
11 1. Redistributions of source code must retain the above copyright
12 notice, this list of conditions and the following disclaimer.
13 2. Redistributions in binary form must reproduce the above copyright
14 notice, this list of conditions and the following disclaimer in the
15 documentation and/or other materials provided with the distribution.
16 3. Neither the name of the project nor the names of its contributors
17 may be used to endorse or promote products derived from this software
18 without specific prior written permission.
19
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 SUCH DAMAGE.
31 */
32
33 /* Includes corrections thanks to Monica Swanson @ Source Technologies */
34
35 #include "common.h"
36 #include "maxicode.h"
37 #include "reedsol.h"
38 #include <string.h>
39 #include <stdlib.h>
40
41 int maxi_codeword[144];
42
43 /* Handles error correction of primary message */
maxi_do_primary_check()44 void maxi_do_primary_check() {
45 unsigned char data[15];
46 unsigned char results[15];
47 int j;
48 int datalen = 10;
49 int ecclen = 10;
50
51 rs_init_gf(0x43);
52 rs_init_code(ecclen, 1);
53
54 for (j = 0; j < datalen; j += 1)
55 data[j] = maxi_codeword[j];
56
57 rs_encode(datalen, data, results);
58
59 for (j = 0; j < ecclen; j += 1)
60 maxi_codeword[ datalen + j] = results[ecclen - 1 - j];
61 rs_free();
62 }
63
64 /* Handles error correction of odd characters in secondary */
maxi_do_secondary_chk_odd(int ecclen)65 void maxi_do_secondary_chk_odd(int ecclen) {
66 unsigned char data[100];
67 unsigned char results[30];
68 int j;
69 int datalen = 68;
70
71 rs_init_gf(0x43);
72 rs_init_code(ecclen, 1);
73
74 if (ecclen == 20)
75 datalen = 84;
76
77 for (j = 0; j < datalen; j += 1)
78 if (j & 1) // odd
79 data[(j - 1) / 2] = maxi_codeword[j + 20];
80
81 rs_encode(datalen / 2, data, results);
82
83 for (j = 0; j < (ecclen); j += 1)
84 maxi_codeword[ datalen + (2 * j) + 1 + 20 ] = results[ecclen - 1 - j];
85 rs_free();
86 }
87
88 /* Handles error correction of even characters in secondary */
maxi_do_secondary_chk_even(int ecclen)89 void maxi_do_secondary_chk_even(int ecclen) {
90 unsigned char data[100];
91 unsigned char results[30];
92 int j;
93 int datalen = 68;
94
95 if (ecclen == 20)
96 datalen = 84;
97
98 rs_init_gf(0x43);
99 rs_init_code(ecclen, 1);
100
101 for (j = 0; j < datalen + 1; j += 1)
102 if (!(j & 1)) // even
103 data[j / 2] = maxi_codeword[j + 20];
104
105 rs_encode(datalen / 2, data, results);
106
107 for (j = 0; j < (ecclen); j += 1)
108 maxi_codeword[ datalen + (2 * j) + 20] = results[ecclen - 1 - j];
109 rs_free();
110 }
111
112 /* Moves everything up so that a shift or latch can be inserted */
maxi_bump(int set[],int character[],int bump_posn)113 void maxi_bump(int set[], int character[], int bump_posn) {
114 int i;
115
116 for (i = 143; i > bump_posn; i--) {
117 set[i] = set[i - 1];
118 character[i] = character[i - 1];
119 }
120 }
121
122 /* Format text according to Appendix A */
maxi_text_process(int mode,unsigned char source[],int length,int eci)123 int maxi_text_process(int mode, unsigned char source[], int length, int eci) {
124 /* This code doesn't make use of [Lock in C], [Lock in D]
125 and [Lock in E] and so is not always the most efficient at
126 compressing data, but should suffice for most applications */
127
128 int set[144], character[144], i, j, done, count, current_set;
129
130 if (length > 138) {
131 return ZINT_ERROR_TOO_LONG;
132 }
133
134 for (i = 0; i < 144; i++) {
135 set[i] = -1;
136 character[i] = 0;
137 }
138
139 for (i = 0; i < length; i++) {
140 /* Look up characters in table from Appendix A - this gives
141 value and code set for most characters */
142 set[i] = maxiCodeSet[source[i]];
143 character[i] = maxiSymbolChar[source[i]];
144 }
145
146 /* If a character can be represented in more than one code set,
147 pick which version to use */
148 if (set[0] == 0) {
149 if (character[0] == 13) {
150 character[0] = 0;
151 }
152 set[0] = 1;
153 }
154
155 for (i = 1; i < length; i++) {
156 if (set[i] == 0) {
157 done = 0;
158 /* Special character */
159 if (character[i] == 13) {
160 /* Carriage Return */
161 if (set[i - 1] == 5) {
162 character[i] = 13;
163 set[i] = 5;
164 } else {
165 if ((i != length - 1) && (set[i + 1] == 5)) {
166 character[i] = 13;
167 set[i] = 5;
168 } else {
169 character[i] = 0;
170 set[i] = 1;
171 }
172 }
173 done = 1;
174 }
175
176 if ((character[i] == 28) && (done == 0)) {
177 /* FS */
178 if (set[i - 1] == 5) {
179 character[i] = 32;
180 set[i] = 5;
181 } else {
182 set[i] = set[i - 1];
183 }
184 done = 1;
185 }
186
187 if ((character[i] == 29) && (done == 0)) {
188 /* GS */
189 if (set[i - 1] == 5) {
190 character[i] = 33;
191 set[i] = 5;
192 } else {
193 set[i] = set[i - 1];
194 }
195 done = 1;
196 }
197
198 if ((character[i] == 30) && (done == 0)) {
199 /* RS */
200 if (set[i - 1] == 5) {
201 character[i] = 34;
202 set[i] = 5;
203 } else {
204 set[i] = set[i - 1];
205 }
206 done = 1;
207 }
208
209 if ((character[i] == 32) && (done == 0)) {
210 /* Space */
211 if (set[i - 1] == 1) {
212 character[i] = 32;
213 set[i] = 1;
214 }
215 if (set[i - 1] == 2) {
216 character[i] = 47;
217 set[i] = 2;
218 }
219 if (set[i - 1] >= 3) {
220 if (i != length - 1) {
221 if (set[i + 1] == 1) {
222 character[i] = 32;
223 set[i] = 1;
224 }
225 if (set[i + 1] == 2) {
226 character[i] = 47;
227 set[i] = 2;
228 }
229 if (set[i + 1] >= 3) {
230 character[i] = 59;
231 set[i] = set[i - 1];
232 }
233 } else {
234 character[i] = 59;
235 set[i] = set[i - 1];
236 }
237 }
238 done = 1;
239 }
240
241 if ((character[i] == 44) && (done == 0)) {
242 /* Comma */
243 if (set[i - 1] == 2) {
244 character[i] = 48;
245 set[i] = 2;
246 } else {
247 if ((i != length - 1) && (set[i + 1] == 2)) {
248 character[i] = 48;
249 set[i] = 2;
250 } else {
251 set[i] = 1;
252 }
253 }
254 done = 1;
255 }
256
257 if ((character[i] == 46) && (done == 0)) {
258 /* Full Stop */
259 if (set[i - 1] == 2) {
260 character[i] = 49;
261 set[i] = 2;
262 } else {
263 if ((i != length - 1) && (set[i + 1] == 2)) {
264 character[i] = 49;
265 set[i] = 2;
266 } else {
267 set[i] = 1;
268 }
269 }
270 done = 1;
271 }
272
273 if ((character[i] == 47) && (done == 0)) {
274 /* Slash */
275 if (set[i - 1] == 2) {
276 character[i] = 50;
277 set[i] = 2;
278 } else {
279 if ((i != length - 1) && (set[i + 1] == 2)) {
280 character[i] = 50;
281 set[i] = 2;
282 } else {
283 set[i] = 1;
284 }
285 }
286 done = 1;
287 }
288
289 if ((character[i] == 58) && (done == 0)) {
290 /* Colon */
291 if (set[i - 1] == 2) {
292 character[i] = 51;
293 set[i] = 2;
294 } else {
295 if ((i != length - 1) && (set[i + 1] == 2)) {
296 character[i] = 51;
297 set[i] = 2;
298 } else {
299 set[i] = 1;
300 }
301 }
302 done = 1;
303 }
304 }
305 }
306
307 for (i = length; i < 144; i++) {
308 /* Add the padding */
309 if (set[length - 1] == 2) {
310 set[i] = 2;
311 } else {
312 set[i] = 1;
313 }
314 character[i] = 33;
315 }
316
317 /* Find candidates for number compression */
318 if ((mode == 2) || (mode == 3)) {
319 j = 0;
320 } else {
321 j = 9;
322 }
323 /* Number compression not allowed in primary message */
324 count = 0;
325 for (i = j; i < 143; i++) {
326 if ((set[i] == 1) && ((character[i] >= 48) && (character[i] <= 57))) {
327 /* Character is a number */
328 count++;
329 } else {
330 count = 0;
331 }
332 if (count == 9) {
333 /* Nine digits in a row can be compressed */
334 set[i] = 6;
335 set[i - 1] = 6;
336 set[i - 2] = 6;
337 set[i - 3] = 6;
338 set[i - 4] = 6;
339 set[i - 5] = 6;
340 set[i - 6] = 6;
341 set[i - 7] = 6;
342 set[i - 8] = 6;
343 count = 0;
344 }
345 }
346
347 /* Add shift and latch characters */
348 current_set = 1;
349 i = 0;
350 do {
351
352 if ((set[i] != current_set) && (set[i] != 6)) {
353 switch (set[i]) {
354 case 1:
355 if (set[i + 1] == 1) {
356 if (set[i + 2] == 1) {
357 if (set[i + 3] == 1) {
358 /* Latch A */
359 maxi_bump(set, character, i);
360 character[i] = 63;
361 current_set = 1;
362 length++;
363 } else {
364 /* 3 Shift A */
365 maxi_bump(set, character, i);
366 character[i] = 57;
367 length++;
368 i += 2;
369 }
370 } else {
371 /* 2 Shift A */
372 maxi_bump(set, character, i);
373 character[i] = 56;
374 length++;
375 i++;
376 }
377 } else {
378 /* Shift A */
379 maxi_bump(set, character, i);
380 character[i] = 59;
381 length++;
382 }
383 break;
384 case 2:
385 if (set[i + 1] == 2) {
386 /* Latch B */
387 maxi_bump(set, character, i);
388 character[i] = 63;
389 current_set = 2;
390 length++;
391 } else {
392 /* Shift B */
393 maxi_bump(set, character, i);
394 character[i] = 59;
395 length++;
396 }
397 break;
398 case 3:
399 /* Shift C */
400 maxi_bump(set, character, i);
401 character[i] = 60;
402 length++;
403 break;
404 case 4:
405 /* Shift D */
406 maxi_bump(set, character, i);
407 character[i] = 61;
408 length++;
409 break;
410 case 5:
411 /* Shift E */
412 maxi_bump(set, character, i);
413 character[i] = 62;
414 length++;
415 break;
416 }
417 i++;
418 }
419 i++;
420 } while (i < 144);
421
422 /* Number compression has not been forgotten! - It's handled below */
423 i = 0;
424 do {
425 if (set[i] == 6) {
426 /* Number compression */
427 char substring[11];
428 int value;
429
430 for (j = 0; j < 9; j++) {
431 substring[j] = character[i + j];
432 }
433 substring[9] = '\0';
434 value = atoi(substring);
435
436 character[i] = 31; /* NS */
437 character[i + 1] = (value & 0x3f000000) >> 24;
438 character[i + 2] = (value & 0xfc0000) >> 18;
439 character[i + 3] = (value & 0x3f000) >> 12;
440 character[i + 4] = (value & 0xfc0) >> 6;
441 character[i + 5] = (value & 0x3f);
442
443 i += 6;
444 for (j = i; j < 140; j++) {
445 set[j] = set[j + 3];
446 character[j] = character[j + 3];
447 }
448 length -= 3;
449 } else {
450 i++;
451 }
452 } while (i <= 143);
453
454 /* Insert ECI at the beginning of message if needed */
455 /* Encode ECI assignment numbers according to table 3 */
456 if (eci != 3) {
457 maxi_bump(set, character, 0);
458 character[0] = 27; // ECI
459 if (eci <= 31) {
460 maxi_bump(set, character, 1);
461 character[1] = eci;
462 length += 2;
463 }
464 if ((eci >= 32) && (eci <= 1023)) {
465 maxi_bump(set, character, 1);
466 maxi_bump(set, character, 1);
467 character[1] = 0x20 + ((eci >> 6) & 0x0F);
468 character[2] = eci & 0x3F;
469 length += 3;
470 }
471 if ((eci >= 1024) && (eci <= 32767)) {
472 maxi_bump(set, character, 1);
473 maxi_bump(set, character, 1);
474 maxi_bump(set, character, 1);
475 character[1] = 0x30 + ((eci >> 12) & 0x03);
476 character[2] = (eci >> 6) & 0x3F;
477 character[3] = eci & 0x3F;
478 length += 4;
479 }
480 if (eci >= 32768) {
481 maxi_bump(set, character, 1);
482 maxi_bump(set, character, 1);
483 maxi_bump(set, character, 1);
484 maxi_bump(set, character, 1);
485 character[1] = 0x38 + ((eci >> 18) & 0x02);
486 character[2] = (eci >> 12) & 0x3F;
487 character[3] = (eci >> 6) & 0x3F;
488 character[4] = eci & 0x3F;
489 length += 5;
490 }
491 }
492
493 if (((mode == 2) || (mode == 3)) && (length > 84)) {
494 return ZINT_ERROR_TOO_LONG;
495 }
496
497 if (((mode == 4) || (mode == 6)) && (length > 93)) {
498 return ZINT_ERROR_TOO_LONG;
499 }
500
501 if ((mode == 5) && (length > 77)) {
502 return ZINT_ERROR_TOO_LONG;
503 }
504
505
506 /* Copy the encoded text into the codeword array */
507 if ((mode == 2) || (mode == 3)) {
508 for (i = 0; i < 84; i++) { /* secondary only */
509 maxi_codeword[i + 20] = character[i];
510 }
511 }
512
513 if ((mode == 4) || (mode == 6)) {
514 for (i = 0; i < 9; i++) { /* primary */
515 maxi_codeword[i + 1] = character[i];
516 }
517 for (i = 0; i < 84; i++) { /* secondary */
518 maxi_codeword[i + 20] = character[i + 9];
519 }
520 }
521
522 if (mode == 5) {
523 for (i = 0; i < 9; i++) { /* primary */
524 maxi_codeword[i + 1] = character[i];
525 }
526 for (i = 0; i < 68; i++) { /* secondary */
527 maxi_codeword[i + 20] = character[i + 9];
528 }
529 }
530
531 return 0;
532 }
533
534 /* Format structured primary for Mode 2 */
maxi_do_primary_2(char postcode[],int country,int service)535 void maxi_do_primary_2(char postcode[], int country, int service) {
536 size_t postcode_length;
537 int postcode_num, i;
538
539 for (i = 0; i < 10; i++) {
540 if ((postcode[i] < '0') || (postcode[i] > '9')) {
541 postcode[i] = '\0';
542 }
543 }
544
545 postcode_length = strlen(postcode);
546 postcode_num = atoi(postcode);
547
548 maxi_codeword[0] = ((postcode_num & 0x03) << 4) | 2;
549 maxi_codeword[1] = ((postcode_num & 0xfc) >> 2);
550 maxi_codeword[2] = ((postcode_num & 0x3f00) >> 8);
551 maxi_codeword[3] = ((postcode_num & 0xfc000) >> 14);
552 maxi_codeword[4] = ((postcode_num & 0x3f00000) >> 20);
553 maxi_codeword[5] = ((postcode_num & 0x3c000000) >> 26) | ((postcode_length & 0x3) << 4);
554 maxi_codeword[6] = ((postcode_length & 0x3c) >> 2) | ((country & 0x3) << 4);
555 maxi_codeword[7] = (country & 0xfc) >> 2;
556 maxi_codeword[8] = ((country & 0x300) >> 8) | ((service & 0xf) << 2);
557 maxi_codeword[9] = ((service & 0x3f0) >> 4);
558 }
559
560 /* Format structured primary for Mode 3 */
maxi_do_primary_3(char postcode[],int country,int service)561 void maxi_do_primary_3(char postcode[], int country, int service) {
562 int i, h;
563
564 h = strlen(postcode);
565 to_upper((unsigned char*) postcode);
566 for (i = 0; i < h; i++) {
567 if ((postcode[i] >= 'A') && (postcode[i] <= 'Z')) {
568 /* (Capital) letters shifted to Code Set A values */
569 postcode[i] -= 64;
570 }
571 if (((postcode[i] == 27) || (postcode[i] == 31)) || ((postcode[i] == 33) || (postcode[i] >= 59))) {
572 /* Not a valid postcode character */
573 postcode[i] = ' ';
574 }
575 /* Input characters lower than 27 (NUL - SUB) in postcode are
576 interpreted as capital letters in Code Set A (e.g. LF becomes 'J') */
577 }
578
579 maxi_codeword[0] = ((postcode[5] & 0x03) << 4) | 3;
580 maxi_codeword[1] = ((postcode[4] & 0x03) << 4) | ((postcode[5] & 0x3c) >> 2);
581 maxi_codeword[2] = ((postcode[3] & 0x03) << 4) | ((postcode[4] & 0x3c) >> 2);
582 maxi_codeword[3] = ((postcode[2] & 0x03) << 4) | ((postcode[3] & 0x3c) >> 2);
583 maxi_codeword[4] = ((postcode[1] & 0x03) << 4) | ((postcode[2] & 0x3c) >> 2);
584 maxi_codeword[5] = ((postcode[0] & 0x03) << 4) | ((postcode[1] & 0x3c) >> 2);
585 maxi_codeword[6] = ((postcode[0] & 0x3c) >> 2) | ((country & 0x3) << 4);
586 maxi_codeword[7] = (country & 0xfc) >> 2;
587 maxi_codeword[8] = ((country & 0x300) >> 8) | ((service & 0xf) << 2);
588 maxi_codeword[9] = ((service & 0x3f0) >> 4);
589 }
590
maxicode(struct zint_symbol * symbol,unsigned char local_source[],int length)591 int maxicode(struct zint_symbol *symbol, unsigned char local_source[], int length) {
592 int i, j, block, bit, mode, countrycode = 0, service = 0, lp = 0;
593 int bit_pattern[7], internal_error = 0, eclen;
594 char postcode[12], countrystr[4], servicestr[4];
595
596 mode = symbol->option_1;
597 strcpy(postcode, "");
598 strcpy(countrystr, "");
599 strcpy(servicestr, "");
600
601 memset(maxi_codeword, 0, sizeof (maxi_codeword));
602
603 if (mode == -1) { /* If mode is unspecified */
604 lp = strlen(symbol->primary);
605 if (lp == 0) {
606 mode = 4;
607 } else {
608 mode = 2;
609 for (i = 0; i < 10 && i < lp; i++) {
610 if ((symbol->primary[i] < 48) || (symbol->primary[i] > 57)) {
611 mode = 3;
612 break;
613 }
614 }
615 }
616 }
617
618 if ((mode < 2) || (mode > 6)) { /* Only codes 2 to 6 supported */
619 strcpy(symbol->errtxt, "550: Invalid Maxicode Mode");
620 return ZINT_ERROR_INVALID_OPTION;
621 }
622
623 if ((mode == 2) || (mode == 3)) { /* Modes 2 and 3 need data in symbol->primary */
624 if (lp == 0) { /* Mode set manually means lp doesn't get set */
625 lp = strlen(symbol->primary);
626 }
627 if (lp != 15) {
628 strcpy(symbol->errtxt, "551: Invalid Primary String");
629 return ZINT_ERROR_INVALID_DATA;
630 }
631
632 for (i = 9; i < 15; i++) { /* check that country code and service are numeric */
633 if ((symbol->primary[i] < '0') || (symbol->primary[i] > '9')) {
634 strcpy(symbol->errtxt, "552: Invalid Primary String");
635 return ZINT_ERROR_INVALID_DATA;
636 }
637 }
638
639 memcpy(postcode, symbol->primary, 9);
640 postcode[9] = '\0';
641
642 if (mode == 2) {
643 for (i = 0; i < 10; i++) {
644 if (postcode[i] == ' ') {
645 postcode[i] = '\0';
646 }
647 }
648 } else if (mode == 3) {
649 postcode[6] = '\0';
650 }
651
652 countrystr[0] = symbol->primary[9];
653 countrystr[1] = symbol->primary[10];
654 countrystr[2] = symbol->primary[11];
655 countrystr[3] = '\0';
656
657 servicestr[0] = symbol->primary[12];
658 servicestr[1] = symbol->primary[13];
659 servicestr[2] = symbol->primary[14];
660 servicestr[3] = '\0';
661
662 countrycode = atoi(countrystr);
663 service = atoi(servicestr);
664
665 if (mode == 2) {
666 maxi_do_primary_2(postcode, countrycode, service);
667 }
668 if (mode == 3) {
669 maxi_do_primary_3(postcode, countrycode, service);
670 }
671 } else {
672 maxi_codeword[0] = mode;
673 }
674
675 i = maxi_text_process(mode, local_source, length, symbol->eci);
676 if (i == ZINT_ERROR_TOO_LONG) {
677 strcpy(symbol->errtxt, "553: Input data too long");
678 return i;
679 }
680
681 /* All the data is sorted - now do error correction */
682 maxi_do_primary_check(); /* always EEC */
683
684 if (mode == 5)
685 eclen = 56; // 68 data codewords , 56 error corrections
686 else
687 eclen = 40; // 84 data codewords, 40 error corrections
688
689 maxi_do_secondary_chk_even(eclen / 2); // do error correction of even
690 maxi_do_secondary_chk_odd(eclen / 2); // do error correction of odd
691
692 /* Copy data into symbol grid */
693 for (i = 0; i < 33; i++) {
694 for (j = 0; j < 30; j++) {
695 block = (MaxiGrid[(i * 30) + j] + 5) / 6;
696 bit = (MaxiGrid[(i * 30) + j] + 5) % 6;
697
698 if (block != 0) {
699
700 bit_pattern[0] = (maxi_codeword[block - 1] & 0x20) >> 5;
701 bit_pattern[1] = (maxi_codeword[block - 1] & 0x10) >> 4;
702 bit_pattern[2] = (maxi_codeword[block - 1] & 0x8) >> 3;
703 bit_pattern[3] = (maxi_codeword[block - 1] & 0x4) >> 2;
704 bit_pattern[4] = (maxi_codeword[block - 1] & 0x2) >> 1;
705 bit_pattern[5] = (maxi_codeword[block - 1] & 0x1);
706
707 if (bit_pattern[bit] != 0) {
708 set_module(symbol, i, j);
709 }
710 }
711 }
712 }
713
714 /* Add orientation markings */
715 set_module(symbol, 0, 28); // Top right filler
716 set_module(symbol, 0, 29);
717 set_module(symbol, 9, 10); // Top left marker
718 set_module(symbol, 9, 11);
719 set_module(symbol, 10, 11);
720 set_module(symbol, 15, 7); // Left hand marker
721 set_module(symbol, 16, 8);
722 set_module(symbol, 16, 20); // Right hand marker
723 set_module(symbol, 17, 20);
724 set_module(symbol, 22, 10); // Bottom left marker
725 set_module(symbol, 23, 10);
726 set_module(symbol, 22, 17); // Bottom right marker
727 set_module(symbol, 23, 17);
728
729 symbol->width = 30;
730 symbol->rows = 33;
731
732 return internal_error;
733 }
734