1<?php
2
3namespace Mpdf\Barcode;
4
5/**
6 * EAN13 and UPC-A barcodes.
7 * EAN13: European Article Numbering international retail product code
8 * UPC-A: Universal product code seen on almost all retail products in the USA and Canada
9 * UPC-E: Short version of UPC symbol
10 */
11class EanUpc extends \Mpdf\Barcode\AbstractBarcode implements \Mpdf\Barcode\BarcodeInterface
12{
13
14	/**
15	 * @param string $code
16	 * @param int $length
17	 * @param float $leftMargin
18	 * @param float $rightMargin
19	 * @param float $xDim
20	 * @param float $barHeight
21	 */
22	public function __construct($code, $length, $leftMargin, $rightMargin, $xDim, $barHeight)
23	{
24		$this->init($code, $length);
25
26		$this->data['lightmL'] = $leftMargin; // LEFT light margin =  x X-dim (http://www.gs1uk.org)
27		$this->data['lightmR'] = $rightMargin; // RIGHT light margin =  x X-dim (http://www.gs1uk.org)
28		$this->data['nom-X'] = $xDim; // Nominal value for X-dim in mm (http://www.gs1uk.org)
29		$this->data['nom-H'] = $barHeight; // Nominal bar height in mm incl. numerals (http://www.gs1uk.org)
30	}
31
32	/**
33	 * @param string $code
34	 * @param int $length
35	 */
36	private function init($code, $length)
37	{
38		if (preg_match('/[\D]+/', $code)) {
39			throw new \Mpdf\Barcode\BarcodeException(sprintf('Invalid EAN UPC barcode value "%s"', $code));
40		}
41
42		$upce = false;
43		$checkdigit = false;
44
45		if ($length == 6) {
46			$length = 12; // UPC-A
47			$upce = true; // UPC-E mode
48		}
49		$dataLength = $length - 1;
50
51		// Padding
52		$code = str_pad($code, $dataLength, '0', STR_PAD_LEFT);
53		$codeLength = strlen($code);
54
55		// Calculate check digit
56		$sum_a = 0;
57		for ($i = 1; $i < $dataLength; $i += 2) {
58			$sum_a += $code[$i];
59		}
60
61		if ($length > 12) {
62			$sum_a *= 3;
63		}
64		$sum_b = 0;
65		for ($i = 0; $i < $dataLength; $i += 2) {
66			$sum_b += ($code[$i]);
67		}
68
69		if ($length < 13) {
70			$sum_b *= 3;
71		}
72
73		$r = ($sum_a + $sum_b) % 10;
74		if ($r > 0) {
75			$r = (10 - $r);
76		}
77
78		if ($codeLength == $dataLength) {
79			// Add check digit
80			$code .= $r;
81			$checkdigit = $r;
82		} elseif ($r !== (int) $code[$dataLength]) {
83			// Wrong checkdigit
84			throw new \Mpdf\Barcode\BarcodeException(sprintf('Invalid EAN UPC barcode value "%s"', $code));
85		}
86
87		if ($length == 12) {
88			// UPC-A
89			$code = '0' . $code;
90			++$length;
91		}
92
93		if ($upce) {
94			// Convert UPC-A to UPC-E
95			$tmp = substr($code, 4, 3);
96			$prodCode = (int) substr($code, 7, 5); // product code
97			$invalidUpce = false;
98			if (($tmp == '000') or ($tmp == '100') or ($tmp == '200')) {
99				// Manufacturer code ends in 000, 100, or 200
100				$upceCode = substr($code, 2, 2) . substr($code, 9, 3) . substr($code, 4, 1);
101				if ($prodCode > 999) {
102					$invalidUpce = true;
103				}
104			} else {
105				$tmp = substr($code, 5, 2);
106				if ($tmp == '00') {
107					// Manufacturer code ends in 00
108					$upceCode = substr($code, 2, 3) . substr($code, 10, 2) . '3';
109					if ($prodCode > 99) {
110						$invalidUpce = true;
111					}
112				} else {
113					$tmp = substr($code, 6, 1);
114					if ($tmp == '0') {
115						// Manufacturer code ends in 0
116						$upceCode = substr($code, 2, 4) . substr($code, 11, 1) . '4';
117						if ($prodCode > 9) {
118							$invalidUpce = true;
119						}
120					} else {
121						// Manufacturer code does not end in zero
122						$upceCode = substr($code, 2, 5) . substr($code, 11, 1);
123						if ($prodCode > 9) {
124							$invalidUpce = true;
125						}
126					}
127				}
128			}
129
130			if ($invalidUpce) {
131				throw new \Mpdf\Barcode\BarcodeException('UPC-A cannot produce a valid UPC-E barcode');
132			}
133		}
134
135		// Convert digits to bars
136		$codes = [
137			'A' => [// left odd parity
138				'0' => '0001101',
139				'1' => '0011001',
140				'2' => '0010011',
141				'3' => '0111101',
142				'4' => '0100011',
143				'5' => '0110001',
144				'6' => '0101111',
145				'7' => '0111011',
146				'8' => '0110111',
147				'9' => '0001011'],
148			'B' => [// left even parity
149				'0' => '0100111',
150				'1' => '0110011',
151				'2' => '0011011',
152				'3' => '0100001',
153				'4' => '0011101',
154				'5' => '0111001',
155				'6' => '0000101',
156				'7' => '0010001',
157				'8' => '0001001',
158				'9' => '0010111'],
159			'C' => [// right
160				'0' => '1110010',
161				'1' => '1100110',
162				'2' => '1101100',
163				'3' => '1000010',
164				'4' => '1011100',
165				'5' => '1001110',
166				'6' => '1010000',
167				'7' => '1000100',
168				'8' => '1001000',
169				'9' => '1110100']
170		];
171
172		$parities = [
173			'0' => ['A', 'A', 'A', 'A', 'A', 'A'],
174			'1' => ['A', 'A', 'B', 'A', 'B', 'B'],
175			'2' => ['A', 'A', 'B', 'B', 'A', 'B'],
176			'3' => ['A', 'A', 'B', 'B', 'B', 'A'],
177			'4' => ['A', 'B', 'A', 'A', 'B', 'B'],
178			'5' => ['A', 'B', 'B', 'A', 'A', 'B'],
179			'6' => ['A', 'B', 'B', 'B', 'A', 'A'],
180			'7' => ['A', 'B', 'A', 'B', 'A', 'B'],
181			'8' => ['A', 'B', 'A', 'B', 'B', 'A'],
182			'9' => ['A', 'B', 'B', 'A', 'B', 'A']
183		];
184
185		$upceParities = [];
186		$upceParities[0] = [
187			'0' => ['B', 'B', 'B', 'A', 'A', 'A'],
188			'1' => ['B', 'B', 'A', 'B', 'A', 'A'],
189			'2' => ['B', 'B', 'A', 'A', 'B', 'A'],
190			'3' => ['B', 'B', 'A', 'A', 'A', 'B'],
191			'4' => ['B', 'A', 'B', 'B', 'A', 'A'],
192			'5' => ['B', 'A', 'A', 'B', 'B', 'A'],
193			'6' => ['B', 'A', 'A', 'A', 'B', 'B'],
194			'7' => ['B', 'A', 'B', 'A', 'B', 'A'],
195			'8' => ['B', 'A', 'B', 'A', 'A', 'B'],
196			'9' => ['B', 'A', 'A', 'B', 'A', 'B']
197		];
198
199		$upceParities[1] = [
200			'0' => ['A', 'A', 'A', 'B', 'B', 'B'],
201			'1' => ['A', 'A', 'B', 'A', 'B', 'B'],
202			'2' => ['A', 'A', 'B', 'B', 'A', 'B'],
203			'3' => ['A', 'A', 'B', 'B', 'B', 'A'],
204			'4' => ['A', 'B', 'A', 'A', 'B', 'B'],
205			'5' => ['A', 'B', 'B', 'A', 'A', 'B'],
206			'6' => ['A', 'B', 'B', 'B', 'A', 'A'],
207			'7' => ['A', 'B', 'A', 'B', 'A', 'B'],
208			'8' => ['A', 'B', 'A', 'B', 'B', 'A'],
209			'9' => ['A', 'B', 'B', 'A', 'B', 'A']
210		];
211
212		$k = 0;
213		$seq = '101'; // left guard bar
214
215		if ($upce && isset($upceCode)) {
216			$bararray = ['code' => $upceCode, 'maxw' => 0, 'maxh' => 1, 'bcode' => []];
217			$p = $upceParities[$code[1]][$r];
218			for ($i = 0; $i < 6; ++$i) {
219				$seq .= $codes[$p[$i]][$upceCode[$i]];
220			}
221			$seq .= '010101'; // right guard bar
222		} else {
223			$bararray = ['code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => []];
224			$halfLen = ceil($length / 2);
225			if ($length == 8) {
226				for ($i = 0; $i < $halfLen; ++$i) {
227					$seq .= $codes['A'][$code[$i]];
228				}
229			} else {
230				$p = $parities[$code[0]];
231				for ($i = 1; $i < $halfLen; ++$i) {
232					$seq .= $codes[$p[$i - 1]][$code[$i]];
233				}
234			}
235			$seq .= '01010'; // center guard bar
236			for ($i = $halfLen; $i < $length; ++$i) {
237				$seq .= $codes['C'][$code[(int) $i]];
238			}
239			$seq .= '101'; // right guard bar
240		}
241
242		$clen = strlen($seq);
243		$w = 0;
244		for ($i = 0; $i < $clen; ++$i) {
245			$w += 1;
246			if (($i == ($clen - 1)) or (($i < ($clen - 1)) and ($seq[$i] != $seq[($i + 1)]))) {
247				if ($seq[$i] == '1') {
248					$t = true; // bar
249				} else {
250					$t = false; // space
251				}
252				$bararray['bcode'][$k] = ['t' => $t, 'w' => $w, 'h' => 1, 'p' => 0];
253				$bararray['maxw'] += $w;
254				++$k;
255				$w = 0;
256			}
257		}
258		$bararray['checkdigit'] = $checkdigit;
259
260		$this->data = $bararray;
261	}
262
263	/**
264	 * @inheritdoc
265	 */
266	public function getType()
267	{
268		return 'EANUPC';
269	}
270
271}
272