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('Invalid EAN UPC barcode value');
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('Invalid EAN UPC barcode value');
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			if ($invalidUpce) {
130				throw new \Mpdf\Barcode\BarcodeException('UPC-A cannot produce a valid UPC-E barcode');
131			}
132		}
133
134		// Convert digits to bars
135		$codes = [
136			'A' => [// left odd parity
137				'0' => '0001101',
138				'1' => '0011001',
139				'2' => '0010011',
140				'3' => '0111101',
141				'4' => '0100011',
142				'5' => '0110001',
143				'6' => '0101111',
144				'7' => '0111011',
145				'8' => '0110111',
146				'9' => '0001011'],
147			'B' => [// left even parity
148				'0' => '0100111',
149				'1' => '0110011',
150				'2' => '0011011',
151				'3' => '0100001',
152				'4' => '0011101',
153				'5' => '0111001',
154				'6' => '0000101',
155				'7' => '0010001',
156				'8' => '0001001',
157				'9' => '0010111'],
158			'C' => [// right
159				'0' => '1110010',
160				'1' => '1100110',
161				'2' => '1101100',
162				'3' => '1000010',
163				'4' => '1011100',
164				'5' => '1001110',
165				'6' => '1010000',
166				'7' => '1000100',
167				'8' => '1001000',
168				'9' => '1110100']
169		];
170
171		$parities = [
172			'0' => ['A', 'A', 'A', 'A', 'A', 'A'],
173			'1' => ['A', 'A', 'B', 'A', 'B', 'B'],
174			'2' => ['A', 'A', 'B', 'B', 'A', 'B'],
175			'3' => ['A', 'A', 'B', 'B', 'B', 'A'],
176			'4' => ['A', 'B', 'A', 'A', 'B', 'B'],
177			'5' => ['A', 'B', 'B', 'A', 'A', 'B'],
178			'6' => ['A', 'B', 'B', 'B', 'A', 'A'],
179			'7' => ['A', 'B', 'A', 'B', 'A', 'B'],
180			'8' => ['A', 'B', 'A', 'B', 'B', 'A'],
181			'9' => ['A', 'B', 'B', 'A', 'B', 'A']
182		];
183
184		$upceParities = [];
185		$upceParities[0] = [
186			'0' => ['B', 'B', 'B', 'A', 'A', 'A'],
187			'1' => ['B', 'B', 'A', 'B', 'A', 'A'],
188			'2' => ['B', 'B', 'A', 'A', 'B', 'A'],
189			'3' => ['B', 'B', 'A', 'A', 'A', 'B'],
190			'4' => ['B', 'A', 'B', 'B', 'A', 'A'],
191			'5' => ['B', 'A', 'A', 'B', 'B', 'A'],
192			'6' => ['B', 'A', 'A', 'A', 'B', 'B'],
193			'7' => ['B', 'A', 'B', 'A', 'B', 'A'],
194			'8' => ['B', 'A', 'B', 'A', 'A', 'B'],
195			'9' => ['B', 'A', 'A', 'B', 'A', 'B']
196		];
197
198		$upceParities[1] = [
199			'0' => ['A', 'A', 'A', 'B', 'B', 'B'],
200			'1' => ['A', 'A', 'B', 'A', 'B', 'B'],
201			'2' => ['A', 'A', 'B', 'B', 'A', 'B'],
202			'3' => ['A', 'A', 'B', 'B', 'B', 'A'],
203			'4' => ['A', 'B', 'A', 'A', 'B', 'B'],
204			'5' => ['A', 'B', 'B', 'A', 'A', 'B'],
205			'6' => ['A', 'B', 'B', 'B', 'A', 'A'],
206			'7' => ['A', 'B', 'A', 'B', 'A', 'B'],
207			'8' => ['A', 'B', 'A', 'B', 'B', 'A'],
208			'9' => ['A', 'B', 'B', 'A', 'B', 'A']
209		];
210
211		$k = 0;
212		$seq = '101'; // left guard bar
213
214		if ($upce && isset($upceCode)) {
215			$bararray = ['code' => $upceCode, 'maxw' => 0, 'maxh' => 1, 'bcode' => []];
216			$p = $upceParities[$code[1]][$r];
217			for ($i = 0; $i < 6; ++$i) {
218				$seq .= $codes[$p[$i]][$upceCode[$i]];
219			}
220			$seq .= '010101'; // right guard bar
221		} else {
222			$bararray = ['code' => $code, 'maxw' => 0, 'maxh' => 1, 'bcode' => []];
223			$halfLen = ceil($length / 2);
224			if ($length == 8) {
225				for ($i = 0; $i < $halfLen; ++$i) {
226					$seq .= $codes['A'][$code[$i]];
227				}
228			} else {
229				$p = $parities[$code[0]];
230				for ($i = 1; $i < $halfLen; ++$i) {
231					$seq .= $codes[$p[$i - 1]][$code[$i]];
232				}
233			}
234			$seq .= '01010'; // center guard bar
235			for ($i = $halfLen; $i < $length; ++$i) {
236				$seq .= $codes['C'][$code[(int) $i]];
237			}
238			$seq .= '101'; // right guard bar
239		}
240
241		$clen = strlen($seq);
242		$w = 0;
243		for ($i = 0; $i < $clen; ++$i) {
244			$w += 1;
245			if (($i == ($clen - 1)) or (($i < ($clen - 1)) and ($seq[$i] != $seq[($i + 1)]))) {
246				if ($seq[$i] == '1') {
247					$t = true; // bar
248				} else {
249					$t = false; // space
250				}
251				$bararray['bcode'][$k] = ['t' => $t, 'w' => $w, 'h' => 1, 'p' => 0];
252				$bararray['maxw'] += $w;
253				++$k;
254				$w = 0;
255			}
256		}
257		$bararray['checkdigit'] = $checkdigit;
258
259		$this->data = $bararray;
260	}
261
262	/**
263	 * @inheritdoc
264	 */
265	public function getType()
266	{
267		return 'EANUPC';
268	}
269
270}
271