1<?php
2
3namespace Mpdf\Barcode;
4
5/**
6 * CODE 93 - USS-93
7 * Compact code similar to Code 39
8 */
9class Code93 extends \Mpdf\Barcode\AbstractBarcode implements \Mpdf\Barcode\BarcodeInterface
10{
11
12	/**
13	 * @param string $code
14	 */
15	public function __construct($code, $quiet_zone_left = null, $quiet_zone_right = null)
16	{
17		$this->init($code);
18
19		$this->data['nom-X'] = 0.381; // Nominal value for X-dim (bar width) in mm (2 X min. spec.)
20		$this->data['nom-H'] = 10;  // Nominal value for Height of Full bar in mm (non-spec.)
21		$this->data['lightmL'] = ($quiet_zone_left !== null ? $quiet_zone_left : 10); // LEFT light margin =  x X-dim (spec.)
22		$this->data['lightmR'] = ($quiet_zone_right !== null ? $quiet_zone_right : 10); // RIGHT light margin =  x X-dim (spec.)
23		$this->data['lightTB'] = 0; // TOP/BOTTOM light margin =  x X-dim (non-spec.)
24	}
25
26	/**
27	 * @param string $code
28	 */
29	private function init($code)
30	{
31		$chr = [
32			48 => '131112', // 0
33			49 => '111213', // 1
34			50 => '111312', // 2
35			51 => '111411', // 3
36			52 => '121113', // 4
37			53 => '121212', // 5
38			54 => '121311', // 6
39			55 => '111114', // 7
40			56 => '131211', // 8
41			57 => '141111', // 9
42			65 => '211113', // A
43			66 => '211212', // B
44			67 => '211311', // C
45			68 => '221112', // D
46			69 => '221211', // E
47			70 => '231111', // F
48			71 => '112113', // G
49			72 => '112212', // H
50			73 => '112311', // I
51			74 => '122112', // J
52			75 => '132111', // K
53			76 => '111123', // L
54			77 => '111222', // M
55			78 => '111321', // N
56			79 => '121122', // O
57			80 => '131121', // P
58			81 => '212112', // Q
59			82 => '212211', // R
60			83 => '211122', // S
61			84 => '211221', // T
62			85 => '221121', // U
63			86 => '222111', // V
64			87 => '112122', // W
65			88 => '112221', // X
66			89 => '122121', // Y
67			90 => '123111', // Z
68			45 => '121131', // -
69			46 => '311112', // .
70			32 => '311211', //
71			36 => '321111', // $
72			47 => '112131', // /
73			43 => '113121', // +
74			37 => '211131', // %
75			128 => '121221', // ($)
76			129 => '311121', // (/)
77			130 => '122211', // (+)
78			131 => '312111', // (%)
79			42 => '111141', // start-stop
80		];
81
82		$code = strtoupper($code);
83		$encode = [
84			chr(0) => chr(131) . 'U', chr(1) => chr(128) . 'A', chr(2) => chr(128) . 'B', chr(3) => chr(128) . 'C',
85			chr(4) => chr(128) . 'D', chr(5) => chr(128) . 'E', chr(6) => chr(128) . 'F', chr(7) => chr(128) . 'G',
86			chr(8) => chr(128) . 'H', chr(9) => chr(128) . 'I', chr(10) => chr(128) . 'J', chr(11) => '£K',
87			chr(12) => chr(128) . 'L', chr(13) => chr(128) . 'M', chr(14) => chr(128) . 'N', chr(15) => chr(128) . 'O',
88			chr(16) => chr(128) . 'P', chr(17) => chr(128) . 'Q', chr(18) => chr(128) . 'R', chr(19) => chr(128) . 'S',
89			chr(20) => chr(128) . 'T', chr(21) => chr(128) . 'U', chr(22) => chr(128) . 'V', chr(23) => chr(128) . 'W',
90			chr(24) => chr(128) . 'X', chr(25) => chr(128) . 'Y', chr(26) => chr(128) . 'Z', chr(27) => chr(131) . 'A',
91			chr(28) => chr(131) . 'B', chr(29) => chr(131) . 'C', chr(30) => chr(131) . 'D', chr(31) => chr(131) . 'E',
92			chr(32) => ' ', chr(33) => chr(129) . 'A', chr(34) => chr(129) . 'B', chr(35) => chr(129) . 'C',
93			chr(36) => chr(129) . 'D', chr(37) => chr(129) . 'E', chr(38) => chr(129) . 'F', chr(39) => chr(129) . 'G',
94			chr(40) => chr(129) . 'H', chr(41) => chr(129) . 'I', chr(42) => chr(129) . 'J', chr(43) => chr(129) . 'K',
95			chr(44) => chr(129) . 'L', chr(45) => '-', chr(46) => '.', chr(47) => chr(129) . 'O',
96			chr(48) => '0', chr(49) => '1', chr(50) => '2', chr(51) => '3',
97			chr(52) => '4', chr(53) => '5', chr(54) => '6', chr(55) => '7',
98			chr(56) => '8', chr(57) => '9', chr(58) => chr(129) . 'Z', chr(59) => chr(131) . 'F',
99			chr(60) => chr(131) . 'G', chr(61) => chr(131) . 'H', chr(62) => chr(131) . 'I', chr(63) => chr(131) . 'J',
100			chr(64) => chr(131) . 'V', chr(65) => 'A', chr(66) => 'B', chr(67) => 'C',
101			chr(68) => 'D', chr(69) => 'E', chr(70) => 'F', chr(71) => 'G',
102			chr(72) => 'H', chr(73) => 'I', chr(74) => 'J', chr(75) => 'K',
103			chr(76) => 'L', chr(77) => 'M', chr(78) => 'N', chr(79) => 'O',
104			chr(80) => 'P', chr(81) => 'Q', chr(82) => 'R', chr(83) => 'S',
105			chr(84) => 'T', chr(85) => 'U', chr(86) => 'V', chr(87) => 'W',
106			chr(88) => 'X', chr(89) => 'Y', chr(90) => 'Z', chr(91) => chr(131) . 'K',
107			chr(92) => chr(131) . 'L', chr(93) => chr(131) . 'M', chr(94) => chr(131) . 'N', chr(95) => chr(131) . 'O',
108			chr(96) => chr(131) . 'W', chr(97) => chr(130) . 'A', chr(98) => chr(130) . 'B', chr(99) => chr(130) . 'C',
109			chr(100) => chr(130) . 'D', chr(101) => chr(130) . 'E', chr(102) => chr(130) . 'F', chr(103) => chr(130) . 'G',
110			chr(104) => chr(130) . 'H', chr(105) => chr(130) . 'I', chr(106) => chr(130) . 'J', chr(107) => chr(130) . 'K',
111			chr(108) => chr(130) . 'L', chr(109) => chr(130) . 'M', chr(110) => chr(130) . 'N', chr(111) => chr(130) . 'O',
112			chr(112) => chr(130) . 'P', chr(113) => chr(130) . 'Q', chr(114) => chr(130) . 'R', chr(115) => chr(130) . 'S',
113			chr(116) => chr(130) . 'T', chr(117) => chr(130) . 'U', chr(118) => chr(130) . 'V', chr(119) => chr(130) . 'W',
114			chr(120) => chr(130) . 'X', chr(121) => chr(130) . 'Y', chr(122) => chr(130) . 'Z', chr(123) => chr(131) . 'P',
115			chr(124) => chr(131) . 'Q', chr(125) => chr(131) . 'R', chr(126) => chr(131) . 'S', chr(127) => chr(131) . 'T'
116		];
117
118		$code_ext = '';
119		$clen = strlen($code);
120
121		for ($i = 0; $i < $clen; ++$i) {
122			if (ord($code[$i]) > 127) {
123				throw new \Mpdf\Barcode\BarcodeException(sprintf('Invalid character "%s" in CODE93 barcode value "%s"', $code[$i], $code));
124			}
125			$code_ext .= $encode[$code[$i]];
126		}
127
128		// checksum
129		$code_ext .= $this->checksum($code_ext);
130
131		// add start and stop codes
132		$code = '*' . $code_ext . '*';
133		$bararray = ['code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => []];
134		$k = 0;
135		$clen = strlen($code);
136
137		for ($i = 0; $i < $clen; ++$i) {
138			$char = ord($code[$i]);
139			if (!isset($chr[$char])) {
140				// invalid character
141				throw new \Mpdf\Barcode\BarcodeException(sprintf('Invalid CODE93 barcode value "%s"', $code));
142			}
143			for ($j = 0; $j < 6; ++$j) {
144				if (($j % 2) == 0) {
145					$t = true; // bar
146				} else {
147					$t = false; // space
148				}
149				$w = $chr[$char][$j];
150				$bararray['bcode'][$k] = ['t' => $t, 'w' => $w, 'h' => 1, 'p' => 0];
151				$bararray['maxw'] += $w;
152				++$k;
153			}
154		}
155
156		$bararray['bcode'][$k] = ['t' => true, 'w' => 1, 'h' => 1, 'p' => 0];
157		$bararray['maxw'] += 1;
158
159		$this->data = $bararray;
160	}
161
162	/**
163	 * Calculate CODE 93 checksum (modulo 47).
164	 *
165	 * @param string $code
166	 * @return string
167	 */
168	protected function checksum($code)
169	{
170		$chars = [
171			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
172			'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
173			'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
174			'W', 'X', 'Y', 'Z', '-', '.', ' ', '$', '/', '+', '%',
175			'<', '=', '>', '?'
176		];
177
178		// translate special characters
179		$code = strtr($code, chr(128) . chr(131) . chr(129) . chr(130), '<=>?');
180		$len = strlen($code);
181
182		// calculate check digit C
183		$p = 1;
184		$check = 0;
185		for ($i = ($len - 1); $i >= 0; --$i) {
186			$k = array_keys($chars, $code[$i]);
187			$check += ($k[0] * $p);
188			++$p;
189			if ($p > 20) {
190				$p = 1;
191			}
192		}
193		$check %= 47;
194		$c = $chars[$check];
195		$code .= $c;
196
197		// calculate check digit K
198		$p = 1;
199		$check = 0;
200		for ($i = $len; $i >= 0; --$i) {
201			$k = array_keys($chars, $code[$i]);
202			$check += ($k[0] * $p);
203			++$p;
204			if ($p > 15) {
205				$p = 1;
206			}
207		}
208		$check %= 47;
209		$k = $chars[$check];
210		$checksum = $c . $k;
211
212		// resto respecial characters
213		$checksum = strtr($checksum, '<=>?', chr(128) . chr(131) . chr(129) . chr(130));
214
215		return $checksum;
216	}
217
218	/**
219	 * @inheritdoc
220	 */
221	public function getType()
222	{
223		return 'CODE93';
224	}
225
226}
227