1<?php 2 3namespace Mpdf\Barcode; 4 5/** 6 * IMB - Intelligent Mail Barcode - Onecode - USPS-B-3200 7 * 8 * (requires PHP bcmath extension) 9 * 10 * Intelligent Mail barcode is a 65-bar code for use on mail in the United States. 11 * The fields are described as follows: 12 * 13 * - The Barcode Identifier shall be assigned by USPS to encode the presort identification that is currently 14 * printed in human readable form on the optional endorsement line (OEL) as well as for future USPS use. This 15 * shall be two digits, with the second digit in the range of 0-4. The allowable encoding ranges shall be 00-04, 16 * 10-14, 20-24, 30-34, 40-44, 50-54, 60-64, 70-74, 80-84, and 90-94. 17 * 18 * - The Service Type Identifier shall be assigned by USPS for any combination of services requested on the mailpiece. 19 * The allowable encoding range shall be 000-999. Each 3-digit value shall correspond to a particular mail class 20 * with a particular combination of service(s). Each service program, such as OneCode Confirm and OneCode ACS, 21 * shall provide the list of Service Type Identifier values. 22 * 23 * - The Mailer or Customer Identifier shall be assigned by USPS as a unique, 6 or 9 digit number that identifies 24 * a business entity. The allowable encoding range for the 6 digit Mailer ID shall be 000000- 899999, while the 25 * allowable encoding range for the 9 digit Mailer ID shall be 900000000-999999999. 26 * 27 * - The Serial or Sequence Number shall be assigned by the mailer for uniquely identifying and tracking mailpieces. 28 * The allowable encoding range shall be 000000000-999999999 when used with a 6 digit Mailer ID and 000000-999999 29 * when used with a 9 digit Mailer ID. e. The Delivery Point ZIP Code shall be assigned by the mailer for routing 30 * the mailpiece. This shall replace POSTNET for routing the mailpiece to its final delivery point. The length may 31 * be 0, 5, 9, or 11 digits. The allowable encoding ranges shall be no ZIP Code, 00000-99999, 000000000-999999999, 32 * and 00000000000-99999999999. 33 */ 34class Imb extends \Mpdf\Barcode\AbstractBarcode implements \Mpdf\Barcode\BarcodeInterface 35{ 36 37 /** 38 * @param string $code 39 * @param float $xDim 40 * @param float $gapWidth 41 * @param int[] $daft 42 */ 43 public function __construct($code, $xDim, $gapWidth, $daft) 44 { 45 if (!function_exists('bcadd')) { 46 throw new \Mpdf\Barcode\BarcodeException('IMB barcodes require bcmath extension to be loaded.'); 47 } 48 49 $this->init($code, $gapWidth, $daft); 50 51 $this->data['nom-X'] = $xDim; 52 $this->data['nom-H'] = 3.68; // Nominal value for Height of Full bar in mm (spec.) 53 54 // USPS-B-3200 Revision C = 4.623 55 // USPS-B-3200 Revision E = 3.68 56 $this->data['quietL'] = 3.175; // LEFT Quiet margin = mm (spec.) 57 $this->data['quietR'] = 3.175; // RIGHT Quiet margin = mm (spec.) 58 $this->data['quietTB'] = 0.711; // TOP/BOTTOM Quiet margin = mm (spec.) 59 } 60 61 /** 62 * @param string $code 63 * @param float $gapWidth 64 * @param int[] $daft 65 */ 66 private function init($code, $gapWidth, $daft) 67 { 68 $asc_chr = [ 69 4, 0, 2, 6, 3, 5, 1, 9, 8, 7, 1, 2, 0, 6, 4, 8, 2, 9, 5, 3, 0, 1, 3, 7, 4, 6, 8, 9, 2, 0, 5, 1, 9, 4, 70 3, 8, 6, 7, 1, 2, 4, 3, 9, 5, 7, 8, 3, 0, 2, 1, 4, 0, 9, 1, 7, 0, 2, 4, 6, 3, 7, 1, 9, 5, 8 71 ]; 72 73 $dsc_chr = [ 74 7, 1, 9, 5, 8, 0, 2, 4, 6, 3, 5, 8, 9, 7, 3, 0, 6, 1, 7, 4, 6, 8, 9, 2, 5, 1, 7, 5, 4, 3, 8, 7, 6, 0, 2, 75 5, 4, 9, 3, 0, 1, 6, 8, 2, 0, 4, 5, 9, 6, 7, 5, 2, 6, 3, 8, 5, 1, 9, 8, 7, 4, 0, 2, 6, 3 76 ]; 77 78 $asc_pos = [ 79 3, 0, 8, 11, 1, 12, 8, 11, 10, 6, 4, 12, 2, 7, 9, 6, 7, 9, 2, 8, 4, 0, 12, 7, 10, 9, 0, 7, 10, 5, 7, 9, 6, 80 8, 2, 12, 1, 4, 2, 0, 1, 5, 4, 6, 12, 1, 0, 9, 4, 7, 5, 10, 2, 6, 9, 11, 2, 12, 6, 7, 5, 11, 0, 3, 2 81 ]; 82 83 $dsc_pos = [ 84 2, 10, 12, 5, 9, 1, 5, 4, 3, 9, 11, 5, 10, 1, 6, 3, 4, 1, 10, 0, 2, 11, 8, 6, 1, 12, 3, 8, 6, 4, 4, 11, 0, 85 6, 1, 9, 11, 5, 3, 7, 3, 10, 7, 11, 8, 2, 10, 3, 5, 8, 0, 3, 12, 11, 8, 4, 5, 1, 3, 0, 7, 12, 9, 8, 10 86 ]; 87 88 $codeArray = explode('-', $code); 89 $trackingNumber = $codeArray[0]; 90 91 $routingCode = ''; 92 if (isset($codeArray[1])) { 93 $routingCode = $codeArray[1]; 94 } 95 96 // Conversion of Routing Code 97 switch (strlen($routingCode)) { 98 case 0: 99 $binaryCode = 0; 100 break; 101 case 5: 102 $binaryCode = bcadd($routingCode, '1'); 103 break; 104 case 9: 105 $binaryCode = bcadd($routingCode, '100001'); 106 break; 107 case 11: 108 $binaryCode = bcadd($routingCode, '1000100001'); 109 break; 110 default: 111 throw new \Mpdf\Barcode\BarcodeException(sprintf('Invalid MSI routing code "%s"', $routingCode)); 112 } 113 114 $binaryCode = bcmul($binaryCode, 10); 115 $binaryCode = bcadd($binaryCode, $trackingNumber[0]); 116 $binaryCode = bcmul($binaryCode, 5); 117 $binaryCode = bcadd($binaryCode, $trackingNumber[1]); 118 119 $binaryCode .= substr($trackingNumber, 2, 18); 120 121 // convert to hexadecimal 122 $binaryCode = $this->decToHex($binaryCode); 123 124 // pad to get 13 bytes 125 $binaryCode = str_pad($binaryCode, 26, '0', STR_PAD_LEFT); 126 127 // convert string to array of bytes 128 $binaryCodeArray = chunk_split($binaryCode, 2, "\r"); 129 $binaryCodeArray = substr($binaryCodeArray, 0, -1); 130 $binaryCodeArray = explode("\r", $binaryCodeArray); 131 132 // calculate frame check sequence 133 $fcs = $this->imbCrc11Fcs($binaryCodeArray); 134 135 // exclude first 2 bits from first byte 136 $first_byte = sprintf('%2s', dechex((hexdec($binaryCodeArray[0]) << 2) >> 2)); 137 $binaryCode102bit = $first_byte . substr($binaryCode, 2); 138 139 // convert binary data to codewords 140 $codewords = []; 141 $data = $this->hexToDec($binaryCode102bit); 142 $codewords[0] = bcmod($data, 636) * 2; 143 $data = bcdiv($data, 636); 144 145 for ($i = 1; $i < 9; ++$i) { 146 $codewords[$i] = bcmod($data, 1365); 147 $data = bcdiv($data, 1365); 148 } 149 150 $codewords[9] = $data; 151 if (($fcs >> 10) == 1) { 152 $codewords[9] += 659; 153 } 154 155 // generate lookup tables 156 $table2of13 = $this->imbTables(2, 78); 157 $table5of13 = $this->imbTables(5, 1287); 158 159 // convert codewords to characters 160 $characters = []; 161 $bitmask = 512; 162 163 foreach ($codewords as $k => $val) { 164 if ($val <= 1286) { 165 $chrcode = $table5of13[$val]; 166 } else { 167 $chrcode = $table2of13[($val - 1287)]; 168 } 169 if (($fcs & $bitmask) > 0) { 170 // bitwise invert 171 $chrcode = ((~$chrcode) & 8191); 172 } 173 $characters[] = $chrcode; 174 $bitmask /= 2; 175 } 176 177 $characters = array_reverse($characters); 178 179 // build bars 180 $k = 0; 181 $bararray = ['code' => $code, 'maxw' => 0, 'maxh' => $daft['F'], 'bcode' => []]; 182 for ($i = 0; $i < 65; ++$i) { 183 $asc = (($characters[$asc_chr[$i]] & pow(2, $asc_pos[$i])) > 0); 184 $dsc = (($characters[$dsc_chr[$i]] & pow(2, $dsc_pos[$i])) > 0); 185 if ($asc and $dsc) { 186 // full bar (F) 187 $p = 0; 188 $h = $daft['F']; 189 } elseif ($asc) { 190 // ascender (A) 191 $p = 0; 192 $h = $daft['A']; 193 } elseif ($dsc) { 194 // descender (D) 195 $p = $daft['F'] - $daft['D']; 196 $h = $daft['D']; 197 } else { 198 // tracker (T) 199 $p = ($daft['F'] - $daft['T']) / 2; 200 $h = $daft['T']; 201 } 202 $bararray['bcode'][$k++] = ['t' => 1, 'w' => 1, 'h' => $h, 'p' => $p]; 203 // Gap 204 $bararray['bcode'][$k++] = ['t' => 0, 'w' => $gapWidth, 'h' => 1, 'p' => 0]; 205 $bararray['maxw'] += (1 + $gapWidth); 206 } 207 208 unset($bararray['bcode'][($k - 1)]); 209 $bararray['maxw'] -= $gapWidth; 210 211 $this->data = $bararray; 212 } 213 214 /** 215 * Intelligent Mail Barcode calculation of Frame Check Sequence 216 * 217 * @param string[] $codeArray 218 * @return int 219 */ 220 private function imbCrc11Fcs($codeArray) 221 { 222 $genpoly = 0x0F35; // generator polynomial 223 $fcs = 0x07FF; // Frame Check Sequence 224 225 // do most significant byte skipping the 2 most significant bits 226 $data = hexdec($codeArray[0]) << 5; 227 for ($bit = 2; $bit < 8; ++$bit) { 228 if (($fcs ^ $data) & 0x400) { 229 $fcs = ($fcs << 1) ^ $genpoly; 230 } else { 231 $fcs = ($fcs << 1); 232 } 233 $fcs &= 0x7FF; 234 $data <<= 1; 235 } 236 // do rest of bytes 237 for ($byte = 1; $byte < 13; ++$byte) { 238 $data = hexdec($codeArray[$byte]) << 3; 239 for ($bit = 0; $bit < 8; ++$bit) { 240 if (($fcs ^ $data) & 0x400) { 241 $fcs = ($fcs << 1) ^ $genpoly; 242 } else { 243 $fcs = ($fcs << 1); 244 } 245 $fcs &= 0x7FF; 246 $data <<= 1; 247 } 248 } 249 return $fcs; 250 } 251 252 /** 253 * Reverse unsigned short value 254 * 255 * @param int $num 256 * @return int 257 */ 258 private function imbReverseUs($num) 259 { 260 $rev = 0; 261 for ($i = 0; $i < 16; ++$i) { 262 $rev <<= 1; 263 $rev |= ($num & 1); 264 $num >>= 1; 265 } 266 return $rev; 267 } 268 269 /** 270 * Generate Nof13 tables used for Intelligent Mail Barcode 271 * 272 * @param int $n 273 * @param int $size 274 * 275 * @return mixed[] 276 */ 277 private function imbTables($n, $size) 278 { 279 $table = []; 280 $lli = 0; // LUT lower index 281 $lui = $size - 1; // LUT upper index 282 for ($count = 0; $count < 8192; ++$count) { 283 284 $bitCount = 0; 285 for ($bit_index = 0; $bit_index < 13; ++$bit_index) { 286 $bitCount += (int) (($count & (1 << $bit_index)) != 0); 287 } 288 289 // if we don't have the right number of bits on, go on to the next value 290 if ($bitCount == $n) { 291 $reverse = ($this->imbReverseUs($count) >> 3); 292 // if the reverse is less than count, we have already visited this pair before 293 if ($reverse >= $count) { 294 // If count is symmetric, place it at the first free slot from the end of the list. 295 // Otherwise, place it at the first free slot from the beginning of the list AND place $reverse ath the next free slot from the beginning of the list 296 if ($reverse == $count) { 297 $table[$lui] = $count; 298 --$lui; 299 } else { 300 $table[$lli] = $count; 301 ++$lli; 302 $table[$lli] = $reverse; 303 ++$lli; 304 } 305 } 306 } 307 } 308 309 return $table; 310 } 311 312 /** 313 * Convert large integer number to hexadecimal representation. 314 * 315 * @param int $number 316 * @return string 317 */ 318 private function decToHex($number) 319 { 320 $hex = []; 321 322 if ($number == 0) { 323 return '00'; 324 } 325 326 while ($number > 0) { 327 if ($number == 0) { 328 array_push($hex, '0'); 329 } else { 330 array_push($hex, strtoupper(dechex(bcmod($number, '16')))); 331 $number = bcdiv($number, '16', 0); 332 } 333 } 334 335 $hex = array_reverse($hex); 336 return implode($hex); 337 } 338 339 /** 340 * Convert large hexadecimal number to decimal representation (string). 341 * (requires PHP bcmath extension) 342 * 343 * @param string $hex 344 * @return int 345 */ 346 private function hexToDec($hex) 347 { 348 $dec = 0; 349 $bitval = 1; 350 $len = strlen($hex); 351 for ($pos = ($len - 1); $pos >= 0; --$pos) { 352 $dec = bcadd($dec, bcmul(hexdec($hex[$pos]), $bitval)); 353 $bitval = bcmul($bitval, 16); 354 } 355 return $dec; 356 } 357 358 /** 359 * @inheritdoc 360 */ 361 public function getType() 362 { 363 return 'IMB'; 364 } 365 366} 367