| 1 | { lbc_medical.pas - Handles Pharmacode One-Track and Two-Track,
|
|---|
| 2 | CodaBar as well as Code 32
|
|---|
| 3 |
|
|---|
| 4 | Based on Zint (done by Robin Stuart and the Zint team)
|
|---|
| 5 | http://github.com/zint/zint
|
|---|
| 6 | and Pascal adaption by TheUnknownOnes
|
|---|
| 7 | http://theunknownones.net
|
|---|
| 8 |
|
|---|
| 9 | Refactoring: W. Pamler
|
|---|
| 10 | }
|
|---|
| 11 |
|
|---|
| 12 | unit lbc_medical;
|
|---|
| 13 |
|
|---|
| 14 | {$mode objfpc}{$H+}
|
|---|
| 15 |
|
|---|
| 16 | interface
|
|---|
| 17 |
|
|---|
| 18 | uses
|
|---|
| 19 | SysUtils, zint;
|
|---|
| 20 |
|
|---|
| 21 | function pharma_one(ASymbol: PZintSymbol; const ASource: String): Integer;
|
|---|
| 22 | function pharma_two(ASymbol: PZintSymbol; const ASource: String): Integer;
|
|---|
| 23 | function codabar(ASymbol: PZintSymbol; const ASource: String): Integer;
|
|---|
| 24 | function code32(Asymbol: PZintSymbol; const ASource: String): Integer;
|
|---|
| 25 |
|
|---|
| 26 | implementation
|
|---|
| 27 |
|
|---|
| 28 | uses
|
|---|
| 29 | math,
|
|---|
| 30 | lbc_code, lbc_helper;
|
|---|
| 31 |
|
|---|
| 32 | { 'Pharmacode can represent only a single integer from 3 to 131070. Unlike other
|
|---|
| 33 | commonly used one-dimensional barcode schemes, pharmacode does not store the
|
|---|
| 34 | data in a form corresponding to the human-readable digits; the number is
|
|---|
| 35 | encoded in binary, rather than decimal.
|
|---|
| 36 | Pharmacode is read from right to left: with n as the bar position starting
|
|---|
| 37 | at 0 on the right, each narrow bar adds 2n to the value and each wide bar
|
|---|
| 38 | adds 2(2^n).
|
|---|
| 39 | The minimum barcode is 2 bars and the maximum 16, so the smallest number
|
|---|
| 40 | that could be encoded is 3 (2 narrow bars) and the biggest is 131070
|
|---|
| 41 | (16 wide bars).'
|
|---|
| 42 | - http://en.wikipedia.org/wiki/Pharmacode
|
|---|
| 43 |
|
|---|
| 44 | This code uses the One Track Pharamacode calculating algorithm as recommended
|
|---|
| 45 | by the specification at http://www.laetus.com/laetus.php?request=file&id=69 }
|
|---|
| 46 | function pharma_one(ASymbol: PZintSymbol; const ASource: string): Integer;
|
|---|
| 47 | var
|
|---|
| 48 | tester, counter, error_number, src_len: Integer;
|
|---|
| 49 | inter: String;
|
|---|
| 50 | dest: String;
|
|---|
| 51 | begin
|
|---|
| 52 | src_len := Length(ASource);
|
|---|
| 53 |
|
|---|
| 54 | if (src_len > 6) then
|
|---|
| 55 | begin
|
|---|
| 56 | ASymbol^.SetErrorText('Input too long (max 6 characters)');
|
|---|
| 57 | Result := ERROR_TOO_LONG;
|
|---|
| 58 | exit;
|
|---|
| 59 | end;
|
|---|
| 60 |
|
|---|
| 61 | error_number := is_sane(NEON, ASource);
|
|---|
| 62 | if (error_number = ERROR_INVALID_DATA) then
|
|---|
| 63 | begin
|
|---|
| 64 | ASymbol^.SetErrorText('Invalid characters in data');
|
|---|
| 65 | Result := error_number;
|
|---|
| 66 | exit;
|
|---|
| 67 | end;
|
|---|
| 68 |
|
|---|
| 69 | tester := StrToIntDef(ASource, 0);
|
|---|
| 70 | if (tester < 3) or (tester > 131070) then
|
|---|
| 71 | begin
|
|---|
| 72 | ASymbol^.SetErrorText('Data out of range');
|
|---|
| 73 | Result := ERROR_INVALID_DATA;
|
|---|
| 74 | exit;
|
|---|
| 75 | end;
|
|---|
| 76 |
|
|---|
| 77 | inter := '';
|
|---|
| 78 | repeat
|
|---|
| 79 | if tester and 1 = 0 then
|
|---|
| 80 | begin
|
|---|
| 81 | inter := inter + 'W';
|
|---|
| 82 | tester := (tester - 2) div 2;
|
|---|
| 83 | end else
|
|---|
| 84 | begin
|
|---|
| 85 | inter := inter + 'N';
|
|---|
| 86 | tester := (tester - 1) div 2;
|
|---|
| 87 | end;
|
|---|
| 88 | until (tester = 0);
|
|---|
| 89 |
|
|---|
| 90 | dest := '';
|
|---|
| 91 | for counter := Length(inter) downto 1 do
|
|---|
| 92 | begin
|
|---|
| 93 | if (inter[counter] = 'W') then
|
|---|
| 94 | dest := dest + '32'
|
|---|
| 95 | else
|
|---|
| 96 | dest := dest + '12';
|
|---|
| 97 | end;
|
|---|
| 98 | expand(ASymbol, dest);
|
|---|
| 99 |
|
|---|
| 100 | ASymbol^.SetText(PChar(ASource));
|
|---|
| 101 |
|
|---|
| 102 | Result := error_number;
|
|---|
| 103 | end;
|
|---|
| 104 |
|
|---|
| 105 |
|
|---|
| 106 | { This code uses the Two Track Pharmacode defined in the document at
|
|---|
| 107 | http://www.laetus.com/laetus.php?request=file&id=69 and using a modified
|
|---|
| 108 | algorithm from the One Track system. This standard accepts integet values
|
|---|
| 109 | from 4 to 64570080. }
|
|---|
| 110 |
|
|---|
| 111 | function pharma_two_calc(ASymbol: PZintSymbol; const ASource: string;
|
|---|
| 112 | var dest: string): Integer;
|
|---|
| 113 | var
|
|---|
| 114 | tester, counter: Integer;
|
|---|
| 115 | inter: String;
|
|---|
| 116 | error_number: Integer;
|
|---|
| 117 | begin
|
|---|
| 118 | tester := StrToIntDef(ASource, 0);
|
|---|
| 119 |
|
|---|
| 120 | if (tester < 4) or (tester > 64570080) then
|
|---|
| 121 | begin
|
|---|
| 122 | ASymbol^.SetErrorText('Data out of range');
|
|---|
| 123 | Result := ERROR_INVALID_DATA;
|
|---|
| 124 | exit;
|
|---|
| 125 | end;
|
|---|
| 126 |
|
|---|
| 127 | error_number := 0;
|
|---|
| 128 | inter := '';
|
|---|
| 129 | repeat
|
|---|
| 130 | case tester mod 3 of
|
|---|
| 131 | 0: begin
|
|---|
| 132 | inter := inter + '3';
|
|---|
| 133 | tester := (tester - 3) div 3;
|
|---|
| 134 | end;
|
|---|
| 135 | 1: begin
|
|---|
| 136 | inter := inter + '1';
|
|---|
| 137 | tester := (tester - 1) div 3;
|
|---|
| 138 | end;
|
|---|
| 139 | 2: begin
|
|---|
| 140 | inter := inter + '2';
|
|---|
| 141 | tester := (tester - 2) div 3;
|
|---|
| 142 | end;
|
|---|
| 143 | end;
|
|---|
| 144 | until tester = 0;
|
|---|
| 145 |
|
|---|
| 146 | dest := '';
|
|---|
| 147 | for counter := Length(inter) downto 1 do
|
|---|
| 148 | dest := dest + inter[counter];
|
|---|
| 149 |
|
|---|
| 150 | Result := error_number;
|
|---|
| 151 | end;
|
|---|
| 152 |
|
|---|
| 153 | { Draws the patterns for two track pharmacode }
|
|---|
| 154 | function pharma_two(ASymbol: PZintSymbol; const ASource: String): Integer;
|
|---|
| 155 | var
|
|---|
| 156 | height_pattern: string = '';
|
|---|
| 157 | i, src_len, writer, error_number: Integer;
|
|---|
| 158 | begin
|
|---|
| 159 | src_len := Length(ASource);
|
|---|
| 160 |
|
|---|
| 161 | if (src_len > 8) then
|
|---|
| 162 | begin
|
|---|
| 163 | ASymbol^.SetErrorText('Input too long (max 8 characters).');
|
|---|
| 164 | Result := ERROR_TOO_LONG;
|
|---|
| 165 | exit;
|
|---|
| 166 | end;
|
|---|
| 167 |
|
|---|
| 168 | error_number := is_sane(NEON, ASource);
|
|---|
| 169 | if (error_number = ERROR_INVALID_DATA) then
|
|---|
| 170 | begin
|
|---|
| 171 | ASymbol^.SetErrorText('Invalid characters in data');
|
|---|
| 172 | Result := error_number;
|
|---|
| 173 | exit;
|
|---|
| 174 | end;
|
|---|
| 175 |
|
|---|
| 176 | error_number := pharma_two_calc(ASymbol, ASource, height_pattern);
|
|---|
| 177 | if (error_number <> 0) then
|
|---|
| 178 | begin
|
|---|
| 179 | Result := error_number;
|
|---|
| 180 | exit;
|
|---|
| 181 | end;
|
|---|
| 182 |
|
|---|
| 183 | writer := 0;
|
|---|
| 184 | for i := 1 to Length(height_pattern) do
|
|---|
| 185 | begin
|
|---|
| 186 | if ((height_pattern[i] = '2') or (height_pattern[i] = '3')) then
|
|---|
| 187 | begin
|
|---|
| 188 | set_module(ASymbol, 0, writer);
|
|---|
| 189 | end;
|
|---|
| 190 | if ((height_pattern[i] = '1') or (height_pattern[i] = '3')) then
|
|---|
| 191 | begin
|
|---|
| 192 | set_module(ASymbol, 1, writer);
|
|---|
| 193 | end;
|
|---|
| 194 | Inc(writer, 2);
|
|---|
| 195 | end;
|
|---|
| 196 | ASymbol^.rows := 2;
|
|---|
| 197 | ASymbol^.width := writer - 1;
|
|---|
| 198 |
|
|---|
| 199 | ASymbol^.SetText(ASource);
|
|---|
| 200 |
|
|---|
| 201 | Result := error_number;
|
|---|
| 202 | end;
|
|---|
| 203 |
|
|---|
| 204 |
|
|---|
| 205 | { The Codabar system consisting of simple substitution }
|
|---|
| 206 | //chaosben: some changes where made based on the article at http://en.wikipedia.org/wiki/Codabar}
|
|---|
| 207 | function codabar(ASymbol: PZintSymbol; const ASource: String): Integer;
|
|---|
| 208 | const
|
|---|
| 209 | CALCIUM =
|
|---|
| 210 | '0123456789-$:/.+ABCD';
|
|---|
| 211 | CODA_TABLE: array[0..19] of string = (
|
|---|
| 212 | '11111221', '11112211', '11121121', '22111111', '11211211',
|
|---|
| 213 | '21111211', '12111121', '12112111', '12211111', '21121111',
|
|---|
| 214 | '11122111', '11221111', '21112121', '21211121', '21212111',
|
|---|
| 215 | '11212121', '11221211', '12121121', '11121221', '11122211'
|
|---|
| 216 | );
|
|---|
| 217 | CODABAR_DELIMITERS: array[0..7] of Char = (
|
|---|
| 218 | 'A', 'B', 'C', 'D', 'T', 'N', '*', 'E'
|
|---|
| 219 | );
|
|---|
| 220 | var
|
|---|
| 221 | i, j: Integer;
|
|---|
| 222 | localSource: String;
|
|---|
| 223 | begin
|
|---|
| 224 | localSource := Uppercase(ASource);
|
|---|
| 225 |
|
|---|
| 226 | // Replace alternate delimiters
|
|---|
| 227 | for i := 1 to Length(localSource) do
|
|---|
| 228 | for j := 4 to 7 do
|
|---|
| 229 | if localSource[i] = CODABAR_DELIMITERS[j] then
|
|---|
| 230 | localSource[i] := CODABAR_DELIMITERS[j - 4];
|
|---|
| 231 |
|
|---|
| 232 | // Check correct usage of delimiters
|
|---|
| 233 | for i := 2 to Length(localSource) - 1 do
|
|---|
| 234 | begin
|
|---|
| 235 | for j := Low(CODABAR_DELIMITERS) to High(CODABAR_DELIMITERS) do
|
|---|
| 236 | begin
|
|---|
| 237 | if localSource[i] = CODABAR_DELIMITERS[j] then
|
|---|
| 238 | begin
|
|---|
| 239 | ASymbol^.SetErrorText('The character "' + ASource[i] + '" can only be used as first and/or last character.');
|
|---|
| 240 | Result := ERROR_INVALID_DATA;
|
|---|
| 241 | exit;
|
|---|
| 242 | end;
|
|---|
| 243 | end;
|
|---|
| 244 | end;
|
|---|
| 245 |
|
|---|
| 246 | Result := basic_encoder(ASymbol, localsource,
|
|---|
| 247 | 60, CALCIUM, '', CODA_TABLE, '', nil, false);
|
|---|
| 248 |
|
|---|
| 249 | if Result = 0 then
|
|---|
| 250 | ASymbol^.SetText(ASource);
|
|---|
| 251 | end;
|
|---|
| 252 |
|
|---|
| 253 |
|
|---|
| 254 | { Italian Pharmacode }
|
|---|
| 255 | function code32(ASymbol: PZintSymbol; const ASource: String): Integer;
|
|---|
| 256 | const
|
|---|
| 257 | TABLE = '0123456789BCDFGHJKLMNPQRSTUVWXYZ';
|
|---|
| 258 | var
|
|---|
| 259 | i, j, error_number, checksum, checkpart: Integer;
|
|---|
| 260 | localstr: String = '';
|
|---|
| 261 | resultstr: String = '';
|
|---|
| 262 | pharmacode, divisor: Integer;
|
|---|
| 263 | remainder: Integer = 0;
|
|---|
| 264 | codeword: array[0..5] of Integer = (0, 0, 0, 0, 0, 0);
|
|---|
| 265 | begin
|
|---|
| 266 | { Validate the input }
|
|---|
| 267 | if (Length(ASource) > 8) then
|
|---|
| 268 | begin
|
|---|
| 269 | ASymbol^.SetErrorText('Input too long (max 8 characters)');
|
|---|
| 270 | Result := ERROR_TOO_LONG;
|
|---|
| 271 | exit;
|
|---|
| 272 | end;
|
|---|
| 273 |
|
|---|
| 274 | error_number := is_sane(NEON, ASource);
|
|---|
| 275 | if (error_number = ERROR_INVALID_DATA) then
|
|---|
| 276 | begin
|
|---|
| 277 | ASymbol^.SetErrorText('Invalid characters in data');
|
|---|
| 278 | Result := error_number;
|
|---|
| 279 | exit;
|
|---|
| 280 | end;
|
|---|
| 281 |
|
|---|
| 282 | { Add leading zeros as required }
|
|---|
| 283 | localstr := StringOfChar('0', 8 - Length(ASource)) + ASource;
|
|---|
| 284 |
|
|---|
| 285 | { Calculate the check digit }
|
|---|
| 286 | checksum := 0;
|
|---|
| 287 | for i := 0 to 3 do
|
|---|
| 288 | begin
|
|---|
| 289 | j := i * 2 + 1;
|
|---|
| 290 | checkpart := StrToInt(localstr[j]);
|
|---|
| 291 | Inc(checksum, checkpart);
|
|---|
| 292 | checkpart := 2 * StrToInt(localstr[j + 1]);
|
|---|
| 293 | if (checkpart >= 10) then
|
|---|
| 294 | inc(checksum, (checkpart - 10) + 1)
|
|---|
| 295 | else
|
|---|
| 296 | inc(checksum, checkpart);
|
|---|
| 297 | end;
|
|---|
| 298 |
|
|---|
| 299 | { Add check digit to data string }
|
|---|
| 300 | localstr := localstr + IntToStr(checksum mod 10);
|
|---|
| 301 |
|
|---|
| 302 | { Convert string into an integer value }
|
|---|
| 303 | pharmacode := StrToIntDef(localstr, 0);
|
|---|
| 304 |
|
|---|
| 305 | { Convert from decimal to base-32 }
|
|---|
| 306 | divisor := 33554432;
|
|---|
| 307 | for i := 5 downto 0 do
|
|---|
| 308 | begin
|
|---|
| 309 | DivMod(pharmacode, divisor, codeword[i], remainder);
|
|---|
| 310 | pharmacode := remainder;
|
|---|
| 311 | divisor := divisor div 32;
|
|---|
| 312 | end;
|
|---|
| 313 |
|
|---|
| 314 | { Look up values in 'Tabella di conversione' }
|
|---|
| 315 | SetLength(resultstr, 6);
|
|---|
| 316 | for i := 5 downto 0 do
|
|---|
| 317 | resultstr[5 - i + 1] := TABLE[codeword[i]+1];
|
|---|
| 318 |
|
|---|
| 319 | { Plot the barcode using Code 39 }
|
|---|
| 320 | error_number := c39(ASymbol, resultstr);
|
|---|
| 321 | if (error_number <> 0) then
|
|---|
| 322 | begin
|
|---|
| 323 | Result := error_number;
|
|---|
| 324 | exit;
|
|---|
| 325 | end;
|
|---|
| 326 |
|
|---|
| 327 | { Override the normal text output with the Pharmacode number }
|
|---|
| 328 | ASymbol^.SetText('A' + localstr);
|
|---|
| 329 |
|
|---|
| 330 | Result := error_number;
|
|---|
| 331 | end;
|
|---|
| 332 |
|
|---|
| 333 | end.
|
|---|
| 334 |
|
|---|