source: trunk/Packages/lazbarcodes/src/lbc_postal.pas

Last change on this file was 123, checked in by chronos, 3 years ago
  • Added: QR code image visible in contact others tab. It can be saved as image to file.
File size: 19.0 KB
Line 
1{ lbc_postal - Handles postal bar codes
2
3 Based on Zint (done by Robin Stuart and the Zint team)
4 http://github.com/zint/zint
5 and Pascal adaption by TheUnknownOnes
6 http://theunknownones.net
7
8 Refactoring: W. Pamler
9}
10
11unit lbc_postal;
12
13{$mode objfpc}{$H+}
14
15interface
16
17uses
18 SysUtils, zint;
19
20function post_plot(ASymbol: PZintSymbol; const ASource: String): Integer;
21function planet_plot(ASymbol: PZintSymbol; const ASource: String): Integer;
22function korea_post(ASymbol: PZintSymbol; const ASource: String): Integer;
23function fim(ASymbol: PZintSymbol; const ASource: String): Integer;
24function royal_plot(ASymbol: PZintSymbol; const ASource: String): Integer;
25function kix_code(ASymbol: PZintSymbol; const ASource: String): Integer;
26function daft_code(ASymbol: PZintSymbol; const ASource: String): Integer;
27function flattermarken(ASymbol: PZintSymbol; const ASource: String): Integer;
28function japan_post(ASymbol: PZintSymbol; const ASource: String): Integer;
29
30implementation
31
32uses
33 lbc_helper;
34
35const
36 DAFTSET = 'DAFT';
37 KRSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
38 KASUTSET = '1234567890-abcdefgh';
39 CHKASUTSET = '0123456789-abcdefgh';
40 SHKASUTSET = '1234567890-ABCDEFGHIJKLMNOPQRSTUVWXYZ';
41
42 { PostNet number encoding table - In this table L is long as S is short }
43 PNTable: array[0..9] of string = (
44 'LLSSS', 'SSSLL', 'SSLSL', 'SSLLS', 'SLSSL',
45 'SLSLS', 'SLLSS', 'LSSSL', 'LSSLS', 'LSLSS'
46 );
47
48 PLTable: array[0..9] of string = (
49 'SSLLL', 'LLLSS', 'LLSLS', 'LLSSL', 'LSLLS',
50 'LSLSL', 'LSSLL', 'SLLLS','SLLSL', 'SLSLL'
51 );
52
53 RoyalValues: array[0..35] of string = (
54 '11', '12', '13', '14', '15', '10', '21', '22', '23', '24',
55 '25', '20', '31', '32', '33', '34', '35', '30', '41', '42',
56 '43', '44', '45', '40', '51', '52', '53', '54', '55', '50',
57 '01', '02', '03', '04', '05', '00'
58 );
59
60 { 0 = Full, 1 = Ascender, 2 = Descender, 3 = Tracker }
61 RoyalTable: array[0..35] of string = (
62 '3300', '3210', '3201', '2310', '2301',
63 '2211', '3120', '3030', '3021', '2130',
64 '2121', '2031', '3102', '3012', '3003',
65 '2112', '2103', '2013', '1320', '1230',
66 '1221', '0330', '0321', '0231', '1302',
67 '1212', '1203', '0312', '0303', '0213',
68 '1122', '1032', '1023', '0132', '0123',
69 '0033'
70 );
71
72 FlatTable: array[0..9] of string = (
73 '0504', '18', '0117', '0216', '0315',
74 '0414', '0513', '0612', '0711', '0810'
75 );
76
77 KoreaTable: array[0..9] of string = (
78 '1313150613', '0713131313', '0417131313', '1506131313', '0413171313',
79 '17171313', '1315061313', '0413131713', '17131713', '13171713'
80 );
81
82 JapanTable: array[0..18] of string = (
83 '114', '132', '312', '123', '141', '321', '213', '231', '411', '144',
84 '414', '324', '342', '234', '432', '243', '423', '441', '111'
85 );
86
87{ ------------------------------------------------------------------------------
88 PostNet
89
90 Handles the PostNet system used for Zip codes in the US
91-------------------------------------------------------------------------------}
92function postnet(ASymbol: PZintSymbol; const ASource: String;
93 var ADest: String): Integer;
94var
95 i, sum, check_digit, src_len: Integer;
96 error_number: Integer;
97begin
98 src_len := Length(ASource);
99
100 if (src_len > 38) then
101 begin
102 ASymbol^.SetErrorText('Input too long');
103 Result := ERROR_TOO_LONG;
104 exit;
105 end;
106
107 error_number := is_sane(NEON, ASource);
108 if (error_number = ERROR_INVALID_DATA) then
109 begin
110 ASymbol^.SetErrorText('Invalid characters in data');
111 Result := error_number;
112 exit;
113 end;
114
115 sum := 0;
116
117 // START character
118 ADest := 'L';
119
120 for i := 1 to src_len do
121 begin
122 lookup(NEON, PNTable, ASource[i], ADest);
123 Inc(sum, ctoi(ASource[i]));
124 end;
125 check_digit := (10 - sum mod 10) mod 10;
126 ADest := ADest + PNTable[check_digit];
127
128 // STOP character
129 ADest := ADest + 'L';
130
131 Result := error_number;
132end;
133
134{ Puts PostNet barcodes into the pattern matrix }
135function post_plot(ASymbol: PZintSymbol; const ASource: String): Integer;
136var
137 height_pattern: String = '';
138 i, writer: Integer;
139 error_number: Integer;
140begin
141 error_number := postnet(ASymbol, ASource, height_pattern);
142 if (error_number <> 0) then
143 begin
144 Result := error_number;
145 exit;
146 end;
147
148 writer := 0;
149 for i := 1 to Length(height_pattern) do
150 begin
151 if height_pattern[i] = 'L' then
152 set_module(ASymbol, 0, writer);
153 set_module(ASymbol, 1, writer);
154 inc(writer, 3);
155 end;
156
157 ASymbol^.rows := 2;
158 ASymbol^.row_height[0] := 6;
159 ASymbol^.row_height[1] := 6;
160 ASymbol^.width := writer - 1;
161
162 ASymbol^.SetText(ASource);
163
164 Result := error_number;
165end;
166
167
168{ ------------------------------------------------------------------------------
169 USPS Planet
170
171 Handles the PLANET system used for item tracking in the US
172-------------------------------------------------------------------------------}
173function planet(ASymbol: PZintSymbol; const ASource: String;
174 var ADest: String): Integer;
175var
176 i, sum, check_digit, src_len: Integer;
177 error_number: Integer;
178begin
179 src_len := Length(ASource);
180
181 if src_len > 38 then
182 begin
183 ASymbol^.SetErrorText('Input too long');
184 Result := ERROR_TOO_LONG;
185 exit;
186 end;
187
188 error_number := is_sane(NEON, ASource);
189 if (error_number = ERROR_INVALID_DATA) then
190 begin
191 ASymbol^.SetErrorText('Invalid characters in data');
192 Result := error_number;
193 exit;
194 end;
195
196 // START character
197 ADest := 'L';
198
199 sum := 0;
200 for i := 1 to src_len do
201 begin
202 lookup(NEON, PLTable, ASource[i], ADest);
203 inc(sum, ctoi(ASource[i]));
204 end;
205
206 check_digit := (10 - sum mod 10) mod 10;
207 ADest := ADest + PLTable[check_digit];
208
209 // STOP character
210 ADest := ADest + 'L';
211
212 Result := error_number;
213end;
214
215{ Puts PLANET barcodes into the pattern matrix }
216function planet_plot(ASymbol: PZintSymbol; const ASource: String): Integer;
217var
218 height_pattern: string = '';
219 i, writer: Integer;
220 error_number: Integer;
221begin
222 error_number := planet(ASymbol, ASource, height_pattern);
223 if (error_number <> 0) then
224 begin
225 Result := error_number;
226 exit;
227 end;
228
229 writer := 0;
230 for i := 1 to Length(height_pattern) do
231 begin
232 if height_pattern[i] = 'L' then
233 set_module(ASymbol, 0, writer);
234 set_module(ASymbol, 1, writer);
235 inc(writer, 3);
236 end;
237
238 ASymbol^.rows := 2;
239 ASymbol^.row_height[0] := 6;
240 ASymbol^.row_height[1] := 6;
241 ASymbol^.width := writer - 1;
242
243 ASymbol^.SetText(ASource);
244
245 Result := error_number;
246end;
247
248
249{-------------------------------------------------------------------------------
250 Korean Postal Authority
251-------------------------------------------------------------------------------}
252function CheckSum_Korea(ASource: String): String;
253var
254 i, sum, check: Integer;
255begin
256 sum := 0;
257 for i := 1 to Length(ASource) do
258 inc(sum, ctoi(ASource[i]));
259
260 check := 10 - sum mod 10;
261 if check = 10 then check := 0;
262
263 Result := itoc(check);
264end;
265
266function korea_post(ASymbol: PZintSymbol; const ASource: String): Integer;
267const
268 MaxLength = 6;
269var
270 n: Integer;
271 src: String;
272begin
273 if Length(ASource) < MaxLength then
274 begin
275 n := MaxLength - Length(ASource);
276 src := StringOfChar('0', n) + ASource;
277 end else
278 src := ASource;
279
280 ASymbol^.Option := OPTION_ADD_CHECKSUM or OPTION_DISPLAY_CHECKSUM;
281 Result := basic_encoder(ASymbol, src,
282 MaxLength, NEON, '', KoreaTable, '', @CheckSUM_Korea, true
283 );
284end;
285
286
287{-------------------------------------------------------------------------------
288 FIM (facing identification mark)
289
290 Was developed by the United States Postal Service (USPS) to allow automatic
291 facing, or orientation, of the mail piece for cancellation. It also
292 identifies reply mail that uses a preprinted USPS POSTNET barcode symbol.
293 Mail that uses a FIM can be routed to a high-speed sorter.
294
295 The simplest barcode symbology ever! Supported by MS Word, so here it is!
296
297 Glyphs from http://en.wikipedia.org/wiki/Facing_Identification_Mark
298-------------------------------------------------------------------------------}
299function fim(ASymbol: PZintSymbol; const ASource: String): Integer;
300var
301 src_len: Integer;
302 dest: String;
303begin
304 src_len := Length(ASource);
305
306 if (src_len > 1) then
307 begin
308 ASymbol^.SetErrorText('Input too long');
309 Result := ERROR_TOO_LONG;
310 exit;
311 end;
312
313 case ASource[1] of
314 'a', 'A': dest := '111515111';
315 'b', 'B': dest := '13111311131';
316 'c', 'C': dest := '11131313111';
317 'd', 'D': dest := '1111131311111';
318 else
319 ASymbol^.SetErrorText('Invalid characters in data');
320 Result := ERROR_INVALID_DATA;
321 exit;
322 end;
323
324 expand(ASymbol, dest);
325
326 Result := 0;
327end;
328
329
330{-------------------------------------------------------------------------------
331 UK Royal Mail 4-State Customer Code (RM4SCC)
332
333 Handles the 4 State barcodes used in the UK by Royal Mail
334-------------------------------------------------------------------------------}
335function rm4scc(const ASource: String; var ADest: string): Char;
336var
337 i, j: Integer;
338 top, bottom, row, column, check_digit: Integer;
339 values: string;
340begin
341 top := 0;
342 bottom := 0;
343
344 // START character }
345 ADest := '1';
346
347 for i := 1 to Length(ASource) do
348 begin
349 lookup(KRSET, RoyalTable, ASource[i], ADest);
350 j := Pos(ASource[i], KRSET);
351 values := RoyalValues[j - 1]; // -1 because RoyalValues is a string
352 Inc(top, ctoi(values[1]));
353 Inc(bottom, ctoi(values[2]));
354 end;
355
356 // Calculate the check digit
357 row := top mod 6 - 1;
358 column := bottom mod 6 - 1;
359 if (row = -1) then row := 5;
360 if (column = -1) then column := 5;
361 check_digit := 6 * row + column;
362 ADest := ADest + RoyalTable[check_digit];
363
364 // STOP character
365 ADest := ADest + '0';
366
367 result := KRSET[check_digit + 1]; // +1 because KRSET is a string
368end;
369
370{ Puts RM4SCC into the data matrix }
371function royal_plot(ASymbol: PZintSymbol; const ASource: String): Integer;
372var
373 height_pattern: string = '';
374 localStr: String;
375 i, writer, error_number, src_len: Integer;
376begin
377 src_len := Length(ASource);
378
379 if (src_len > 120) then
380 begin
381 ASymbol^.SetErrorText('Input too long');
382 Result := ERROR_TOO_LONG;
383 exit;
384 end;
385
386 localStr := Uppercase(ASource);
387
388 error_number := is_sane(KRSET, localStr);
389 if (error_number = ERROR_INVALID_DATA) then
390 begin
391 ASymbol^.SetErrorText('Invalid characters in data');
392 Result := error_number;
393 exit;
394 end;
395
396 rm4scc(localStr, height_pattern);
397
398 writer := 0;
399 for i := 1 to Length(height_pattern) do
400 begin
401 if (height_pattern[i] in ['1', '0']) then
402 set_module(ASymbol, 0, writer);
403 set_module(ASymbol, 1, writer);
404 if (height_pattern[i] in ['2', '0']) then
405 set_module(ASymbol, 2, writer);
406 Inc(writer, 2);
407 end;
408
409 ASymbol^.rows := 3;
410 ASymbol^.row_height[0] := 3;
411 ASymbol^.row_height[1] := 2;
412 ASymbol^.row_height[2] := 3;
413 ASymbol^.width := writer - 1;
414
415 Result := error_number;
416end;
417
418
419{-------------------------------------------------------------------------------
420 KIX barcode (Klant index)
421
422 Used for mail sorting by the postal service of the Netherlands,
423 Koninklijke TNT Post (Royal TNT Post)
424
425 The same as RM4SCC but without check digit
426
427 Specification at
428 http://www.tntpost.nl/zakelijk/klantenservice/downloads/kIX_code/download.aspx
429
430 https://support.honeywellaidc.com/s/article/What-is-KIX-code-and-is-it-supported-on-Honeywell-barcode-readers
431
432 "The structure of the information in the KIX will be (from left to right):
433 special prefix (optional) two letters fixed length
434 postcode 4 digits, 2 alpha char's. fixed length
435 house number, P.O.Box number maximal 5 digits variable length
436 separator (optional) alpha char: "X" fixed length
437 house number extension (optional) maximal 6 characters variable length"
438-------------------------------------------------------------------------------}
439function kix_code(ASymbol: PZintSymbol; const ASource: String): Integer;
440var
441 height_pattern: string = '';
442 localstr: string;
443 writer, i, error_number, src_len: Integer;
444begin
445 src_len := Length(ASource);
446
447 if (src_len > 18) then
448 begin
449 ASymbol^.SetErrorText('Input too long (max 18 characters).');
450 Result := ERROR_TOO_LONG;
451 exit;
452 end;
453
454 localStr := Uppercase(ASource);
455
456 error_number := is_sane(KRSET, localStr);
457 if (error_number = ERROR_INVALID_DATA) then
458 begin
459 ASymbol^.SetErrorText('Invalid characters in data');
460 Result := error_number;
461 exit;
462 end;
463
464 { Encode data }
465 for i := 1 to 18 do
466 lookup(KRSET, RoyalTable, localstr[i], height_pattern);
467
468 writer := 0;
469 for i := 1 to Length(height_pattern) do
470 begin
471 if (height_pattern[i] in ['1', '0']) then
472 set_module(ASymbol, 0, writer);
473 set_module(ASymbol, 1, writer);
474 if (height_pattern[i] in ['2', '0']) then
475 set_module(ASymbol, 2, writer);
476 Inc(writer, 2);
477 end;
478
479 ASymbol^.rows := 3;
480 ASymbol^.row_height[0] := 3;
481 ASymbol^.row_height[1] := 2;
482 ASymbol^.row_height[2] := 3;
483 ASymbol^.width := writer - 1;
484
485 ASymbol^.SetText(localStr);
486
487 Result := error_number;
488end;
489
490{-------------------------------------------------------------------------------
491 DAFT symbol
492
493 Handles DAFT Code symbols
494 Presumably 'daft' doesn't mean the same thing in Germany as it does in the UK!
495-------------------------------------------------------------------------------}
496function daft_code(ASymbol: PZintSymbol; const ASource: String): Integer;
497var
498 localStr, height_pattern: String;
499 writer, i, error_number, src_len: Integer;
500begin
501 src_len := Length(ASource);
502
503 if (src_len > 50) then
504 begin
505 ASymbol^.SetErrorText('Input too long (max 50 characters)');
506 Result := ERROR_TOO_LONG;
507 exit;
508 end;
509
510 localStr := Uppercase(ASource);
511
512 error_number := is_sane(DAFTSET, localStr);
513 if (error_number = ERROR_INVALID_DATA) then
514 begin
515 ASymbol^.SetErrorText('Invalid characters in data');
516 Result := error_number;
517 exit;
518 end;
519
520 height_pattern := '';
521 for i := 1 to src_len do
522 case localStr[i] of
523 'D': height_pattern := height_pattern + '2';
524 'A': height_pattern := height_pattern + '1';
525 'F': height_pattern := height_pattern + '0';
526 'T': height_pattern := height_pattern + '3';
527 else ;
528 end;
529
530 writer := 0;
531 for i := 1 to Length(height_pattern) do
532 begin
533 if (height_pattern[i] in ['1', '0']) then
534 set_module(ASymbol, 0, writer);
535 set_module(ASymbol, 1, writer);
536 if (height_pattern[i] in ['2', '0']) then
537 set_module(ASymbol, 2, writer);
538 Inc(writer, 2);
539 end;
540
541 ASymbol^.rows := 3;
542 ASymbol^.row_height[0] := 3;
543 ASymbol^.row_height[1] := 2;
544 ASymbol^.row_height[2] := 3;
545 ASymbol^.width := writer - 1;
546
547 Result := error_number;
548end;
549
550
551{-------------------------------------------------------------------------------
552 Flattermarken
553
554 Flattermarken (a German plural; the singular is "flattermarke") are the marks
555 used on the edge of printed material such as sections of books to facilitate
556 their being arranged in the proper order. The symbology allows for the coding
557 of any sequence of numbers through the position of bars. The bars themselves
558 are of the same size and shape, but their position varies.
559-------------------------------------------------------------------------------}
560function flattermarken(ASymbol: PZintSymbol; const ASource: String): Integer;
561var
562 i, error_number, src_len: Integer;
563 dest: string;
564begin
565 src_len := Length(ASource);
566
567 if (src_len > 90) then
568 begin
569 ASymbol^.SetErrorText('Input too long (max 90 characters).');
570 Result := ERROR_TOO_LONG;
571 exit;
572 end;
573
574 error_number := is_sane(NEON, ASource);
575 if (error_number = ERROR_INVALID_DATA) then
576 begin
577 ASymbol^.SetErrorText('Invalid characters in data');
578 Result := error_number;
579 exit;
580 end;
581
582 dest := '';
583 for i := 1 to src_len do
584 lookup(NEON, FlatTable, ASource[i], dest);
585
586 expand(ASymbol, dest);
587
588 Result := error_number;
589end;
590
591
592{-------------------------------------------------------------------------------
593 Japanese Postal Code (Kasutama Barcode)
594-------------------------------------------------------------------------------}
595function japan_post(ASymbol: PZintSymbol; const ASource: String): Integer;
596var
597 writer, inter_posn, i, j, sum, check, error_number, src_len: Integer;
598 check_char: Char;
599 pattern: String;
600 inter: String;
601 localStr: string;
602begin
603 src_len := Length(ASource);
604 localStr := Uppercase(ASource);
605
606 error_number := is_sane(SHKASUTSET, localStr);
607 if (error_number = ERROR_INVALID_DATA) then
608 begin
609 ASymbol^.SetErrorText('Invalid characters in data');
610 Result := error_number;
611 exit;
612 end;
613
614 // Pad character CC4
615 inter := StringOfChar('d', 20);
616
617 i := 1;
618 inter_posn := 1;
619 repeat
620 if (localStr[i] in ['0'..'9', '-']) then
621 begin
622 inter[inter_posn] := localStr[i];
623 Inc(inter_posn);
624 end else
625 begin
626 if (localStr[i] in ['A'..'J']) then
627 begin
628 inter[inter_posn] := 'a';
629 inter[inter_posn + 1] := Char(ord(localStr[i]) - ord('A') + Ord('0'));
630 Inc(inter_posn, 2);
631 end;
632 if (localStr[i] in ['K'..'T']) then
633 begin
634 inter[inter_posn] := 'b';
635 inter[inter_posn + 1] := Char(ord(localStr[i]) - Ord('K') + Ord('0'));
636 Inc(inter_posn, 2);
637 end;
638 if (localStr[i] in ['U'..'Z']) then
639 begin
640 inter[inter_posn] := 'c';
641 inter[inter_posn + 1] := Char(ord(localStr[i]) - Ord('U') + Ord('0'));
642 Inc(inter_posn, 2);
643 end;
644 end;
645 Inc(i);
646 until (i > src_len) or (inter_posn > 20);
647
648 // START character
649 pattern := '13';
650
651 sum := 0;
652 for i := 1 to 20 do
653 begin
654 j := pos(inter[i], KASUTSET) - 1; // -1 to become 0-based again because inter is a string
655 pattern := pattern + JapanTable[j];
656 j := pos(inter[i], CHKASUTSET) - 1;
657 inc(sum, j);
658 end;
659
660 // Calculate check digit
661 check := 19 - sum mod 19;
662 if (check = 19) then check := 0;
663 if (check <= 9) then
664 check_char := Char(check + Ord('0'))
665 else
666 if (check = 10) then
667 check_char := '-'
668 else
669 check_char := Char((check - 11) + Ord('a'));
670
671 j := pos(check_char, KASUTSET) - 1;
672 pattern := pattern + JapanTable[j];
673
674 // STOP character
675 pattern := pattern + '31';
676
677 // Resolve pattern to 4-state symbols
678 writer := 0;
679 for i := 1 to Length(pattern) do
680 begin
681 if (pattern[i] in ['2', '1']) then
682 set_module(ASymbol, 0, writer);
683 set_module(ASymbol, 1, writer);
684 if (pattern[i] in ['3', '1']) then
685 set_module(ASymbol, 2, writer);
686 Inc(writer, 2);
687 end;
688
689 ASymbol^.rows := 3;
690 ASymbol^.row_height[0] := 3;
691 ASymbol^.row_height[1] := 2;
692 ASymbol^.row_height[2] := 3;
693 ASymbol^.width := writer - 1;
694
695 ASymbol^.SetText(localStr);
696
697 Result := error_number;
698end;
699
700
701end.
Note: See TracBrowser for help on using the repository browser.