<?php

namespace Mpdf\QrCode;

use Mpdf\Mpdf;

/**
 * Generateur de QRCode
 * (QR Code is registered trademark of DENSO WAVE INCORPORATED | http://www.denso-wave.com/qrcode/)
 * Fortement inspiré de "QRcode image PHP scripts version 0.50g (C)2000-2005,Y.Swetake"
 *
 * Distribué sous la licence LGPL.
 *
 * @author        Laurent MINGUET <webmaster@spipu.net>
 * @version        0.99
 */
class QrCode
{
	private $version_mx = 40;        // numero de version maximal autorisé

	private $type = 'bin';    // type de donnée

	private $level = 'L';        // ECC

	private $value = '';        // valeur a encoder

	private $length = 0;        // taille de la valeur

	private $version = 0;        // version

	private $size = 0;        // dimension de la zone data

	private $qr_size = 0;        // dimension du QRcode

	private $data_bit = [];    // nb de bit de chacune des valeurs

	private $data_val = [];    // liste des valeurs de bit différents

	private $data_word = [];    // liste des valeurs tout ramené à 8bit

	private $data_cur = 0;        // position courante

	private $data_num = 0;        // position de la dimension

	private $data_bits = 0;        // nom de bit au total

	private $max_data_bit = 0;    // lilmite de nombre de bit maximal pour les datas

	private $max_data_word = 0;    // lilmite de nombre de mot maximal pour les datas

	private $max_word = 0;        // lilmite de nombre de mot maximal en global

	private $ec = 0;

	private $matrix = [];

	private $matrix_remain = 0;

	private $matrix_x_array = [];

	private $matrix_y_array = [];

	private $mask_array = [];

	private $format_information_x1 = [];

	private $format_information_y1 = [];

	private $format_information_x2 = [];

	private $format_information_y2 = [];

	private $rs_block_order = [];

	private $rs_ecc_codewords = 0;

	private $byte_num = 0;

	private $final = [];

	private $disable_border = false;

	/**
	 * @param string $value message a encoder
	 * @param string $level niveau de correction d'erreur (ECC) : L, M, Q, H
	 */
	public function __construct($value, $level = 'L')
	{
		if (!in_array($level, ['L', 'M', 'Q', 'H'])) {
			throw new \Mpdf\QrCode\QrCodeException('ECC not recognized : L, M, Q, H');
		}

		$this->length = strlen($value);
		if (!$this->length) {
			throw new \Mpdf\QrCode\QrCodeException('No data for QrCode');
		}

		$this->level = $level;
		$this->value = $value;

		$this->data_bit = [];
		$this->data_val = [];
		$this->data_cur = 0;
		$this->data_bits = 0;

		$this->encode();
		$this->loadECC();
		$this->makeECC();
		$this->makeMatrix();
	}

	/**
	 * permet de recuperer la taille du QRcode (le nombre de case de côté)
	 *
	 * @return    int    size of qrcode
	 */
	public function getQrSize()
	{
		if ($this->disable_border) {
			return $this->qr_size - 8;
		} else {
			return $this->qr_size;
		}
	}

	public function disableBorder()
	{
		$this->disable_border = true;
	}

	/**
	 * permet d'afficher le QRcode dans un pdf via FPDF
	 *
	 * @param \Mpdf\Mpdf $mpdf    objet fpdf
	 * @param float $x position X
	 * @param float $y position Y
	 * @param float $w taille du qrcode
	 * @param array $background couleur du background (R,V,B)
	 * @param array $color couleur des cases et du border (R,V,B)
	 *
	 * @return boolean true;
	 */
	public function displayFPDF(Mpdf $mpdf, $x, $y, $w, $background = [255, 255, 255], $color = [0, 0, 0])
	{
		$size = $w;
		$s = $size / $this->getQrSize();

		$mpdf->SetDrawColor($color[0], $color[1], $color[2]);
		$mpdf->SetFillColor($background[0], $background[1], $background[2]);

		// rectangle de fond
		if ($this->disable_border) {
			$s_min = 4;
			$s_max = $this->qr_size - 4;
			$mpdf->Rect($x, $y, $size, $size, 'F');
		} else {
			$s_min = 0;
			$s_max = $this->qr_size;
			$mpdf->Rect($x, $y, $size, $size, 'FD');
		}

		$mpdf->SetFillColor($color[0], $color[1], $color[2]);
		for ($j = $s_min; $j < $s_max; $j++) {
			for ($i = $s_min; $i < $s_max; $i++) {
				if ($this->final[$i + $j * $this->qr_size + 1]) {
					$mpdf->Rect($x + ($i - $s_min) * $s, $y + ($j - $s_min) * $s, $s, $s, 'F');
				}
			}
		}

		return true;
	}

	/**
	 * permet d'afficher le QRcode au format HTML, à utiliser avec un style CSS
	 *
	 * @return    boolean true;
	 */
	public function displayHTML()
	{
		if ($this->disable_border) {
			$s_min = 4;
			$s_max = $this->qr_size - 4;
		} else {
			$s_min = 0;
			$s_max = $this->qr_size;
		}
		echo '<table class="qr" cellpadding="0" cellspacing="0">' . "\n";
		for ($y = $s_min; $y < $s_max; $y++) {
			echo '<tr>';
			for ($x = $s_min; $x < $s_max; $x++) {
				echo '<td class="' . ($this->final[$x + $y * $this->qr_size + 1] ? 'on' : 'off') . '"></td>';
			}
			echo '</tr>' . "\n";
		}
		echo '</table>';

		return true;
	}

	/*
	 * permet d'obtenir une image PNG
	 *
	 * @param	float	taille du qrcode
	 * @param	array	couleur du background (R,V,B)
	 * @param	array	couleur des cases et du border (R,V,B)
	 * @param	string	nom du fichier de sortie. si null : sortie directe
	 * @param	integer	qualité de 0 (aucune compression) a 9
	 * @return	boolean	true;
	 */
	public function displayPNG($w = 100, $background = [255, 255, 255], $color = [0, 0, 0], $filename = null, $quality = 0)
	{
		if ($this->disable_border) {
			$s_min = 4;
			$s_max = $this->qr_size - 4;
		} else {
			$s_min = 0;
			$s_max = $this->qr_size;
		}
		$size = $w;
		$s = $size / ($s_max - $s_min);

		// rectangle de fond
		$im = imagecreatetruecolor($size, $size);
		$c_case = imagecolorallocate($im, $color[0], $color[1], $color[2]);
		$c_back = imagecolorallocate($im, $background[0], $background[1], $background[2]);
		imagefilledrectangle($im, 0, 0, $size, $size, $c_back);

		for ($j = $s_min; $j < $s_max; $j++) {
			for ($i = $s_min; $i < $s_max; $i++) {
				if ($this->final[$i + $j * $this->qr_size + 1]) {
					imagefilledrectangle($im, ($i - $s_min) * $s, ($j - $s_min) * $s, ($i - $s_min + 1) * $s - 1, ($j - $s_min + 1) * $s - 1, $c_case);
				}
			}
		}

		if ($filename) {
			imagepng($im, $filename, $quality);
		} else {
			header("Content-type: image/png");
			imagepng($im);
		}
		imagedestroy($im);

		return true;
	}

	private function addData($val, $bit, $next = true)
	{
		$this->data_val[$this->data_cur] = $val;
		$this->data_bit[$this->data_cur] = $bit;
		if ($next) {
			$this->data_cur++;

			return $this->data_cur - 1;
		} else {
			return $this->data_cur;
		}
	}

	private function encode()
	{
		// conversion des datas
		if (preg_match('/[^0-9]/', $this->value)) {
			if (preg_match('/[^0-9A-Z \$\*\%\+\-\.\/\:]/', $this->value)) {
				// type : bin
				$this->type = 'bin';
				$this->addData(4, 4);

				// taille. il faut garder l'indice, car besoin de correction
				$this->data_num = $this->addData($this->length, 8); /* #version 1-9 */
				$data_num_correction = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8];

				// datas
				for ($i = 0; $i < $this->length; $i++) {
					$this->addData(ord(substr($this->value, $i, 1)), 8);
				}
			} else {
				// type : alphanum
				$this->type = 'alphanum';
				$this->addData(2, 4);

				// taille. il faut garder l'indice, car besoin de correction
				$this->data_num = $this->addData($this->length, 9); /* #version 1-9 */
				$data_num_correction = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4];

				// datas
				$an_hash = [
					'0' => 0, '1' => 1, '2' => 2, '3' => 3, '4' => 4, '5' => 5, '6' => 6, '7' => 7, '8' => 8, '9' => 9,
					'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15, 'G' => 16, 'H' => 17, 'I' => 18, 'J' => 19, 'K' => 20, 'L' => 21, 'M' => 22,
					'N' => 23, 'O' => 24, 'P' => 25, 'Q' => 26, 'R' => 27, 'S' => 28, 'T' => 29, 'U' => 30, 'V' => 31, 'W' => 32, 'X' => 33, 'Y' => 34, 'Z' => 35,
					' ' => 36, '$' => 37, '%' => 38, '*' => 39, '+' => 40, '-' => 41, '.' => 42, '/' => 43, ':' => 44];

				for ($i = 0; $i < $this->length; $i++) {
					if (($i % 2) == 0) {
						$this->addData($an_hash[substr($this->value, $i, 1)], 6, false);
					} else {
						$this->addData($this->data_val[$this->data_cur] * 45 + $an_hash[substr($this->value, $i, 1)], 11, true);
					}
				}
				unset($an_hash);

				if (isset($this->data_bit[$this->data_cur])) {
					$this->data_cur++;
				}
			}
		} else {
			// type : num
			$this->type = 'num';
			$this->addData(1, 4);

			//taille. il faut garder l'indice, car besoin de correction
			$this->data_num = $this->addData($this->length, 10); /* #version 1-9 */
			$data_num_correction = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4];

			// datas
			for ($i = 0; $i < $this->length; $i++) {
				if (($i % 3) == 0) {
					$this->addData(substr($this->value, $i, 1), 4, false);
				} else if (($i % 3) == 1) {
					$this->addData($this->data_val[$this->data_cur] * 10 + substr($this->value, $i, 1), 7, false);
				} else {
					$this->addData($this->data_val[$this->data_cur] * 10 + substr($this->value, $i, 1), 10);
				}
			}

			if (isset($this->data_bit[$this->data_cur])) {
				$this->data_cur++;
			}
		}

		// calcul du nombre de bits
		$this->data_bits = 0;
		foreach ($this->data_bit as $bit) {
			$this->data_bits += $bit;
		}

		// code ECC
		$ec_hash = ['L' => 1, 'M' => 0, 'Q' => 3, 'H' => 2];
		$this->ec = $ec_hash[$this->level];

		// tableau de taille limite de bits
		$max_bits = [
			0, 128, 224, 352, 512, 688, 864, 992, 1232, 1456, 1728, 2032, 2320, 2672, 2920, 3320, 3624, 4056, 4504, 5016, 5352,
			5712, 6256, 6880, 7312, 8000, 8496, 9024, 9544, 10136, 10984, 11640, 12328, 13048, 13800, 14496, 15312, 15936, 16816, 17728, 18672,

			152, 272, 440, 640, 864, 1088, 1248, 1552, 1856, 2192, 2592, 2960, 3424, 3688, 4184, 4712, 5176, 5768, 6360, 6888,
			7456, 8048, 8752, 9392, 10208, 10960, 11744, 12248, 13048, 13880, 14744, 15640, 16568, 17528, 18448, 19472, 20528, 21616, 22496, 23648,

			72, 128, 208, 288, 368, 480, 528, 688, 800, 976, 1120, 1264, 1440, 1576, 1784, 2024, 2264, 2504, 2728, 3080,
			3248, 3536, 3712, 4112, 4304, 4768, 5024, 5288, 5608, 5960, 6344, 6760, 7208, 7688, 7888, 8432, 8768, 9136, 9776, 10208,

			104, 176, 272, 384, 496, 608, 704, 880, 1056, 1232, 1440, 1648, 1952, 2088, 2360, 2600, 2936, 3176, 3560, 3880,
			4096, 4544, 4912, 5312, 5744, 6032, 6464, 6968, 7288, 7880, 8264, 8920, 9368, 9848, 10288, 10832, 11408, 12016, 12656, 13328,
		];

		// determination automatique de la version necessaire
		$this->version = 1;
		$i = 1 + 40 * $this->ec;
		$j = $i + 39;
		while ($i <= $j) {
			if ($max_bits[$i] >= $this->data_bits + $data_num_correction[$this->version]) {
				$this->max_data_bit = $max_bits[$i];
				break;
			}
			$i++;
			$this->version++;
		}

		// verification max version
		if ($this->version > $this->version_mx) {
			throw new \Mpdf\QrCode\QrCodeException('QrCode version too large');
		}

		// correctif sur le nombre de bits du strlen de la valeur
		$this->data_bits += $data_num_correction[$this->version];
		$this->data_bit[$this->data_num] += $data_num_correction[$this->version];
		$this->max_data_word = ($this->max_data_bit / 8);

		// nombre de mots maximal
		$max_words_array = [0, 26, 44, 70, 100, 134, 172, 196, 242, 292, 346, 404, 466, 532, 581, 655, 733, 815, 901, 991, 1085, 1156,
			1258, 1364, 1474, 1588, 1706, 1828, 1921, 2051, 2185, 2323, 2465, 2611, 2761, 2876, 3034, 3196, 3362, 3532, 3706];

		$this->max_word = $max_words_array[$this->version];
		$this->size = 17 + 4 * $this->version;

		// nettoyages divers
		unset($max_bits);
		unset($data_num_correction);
		unset($max_words_array);
		unset($ec_hash);

		// terminator
		if ($this->data_bits <= $this->max_data_bit - 4) {
			$this->addData(0, 4);
		} elseif ($this->data_bits < $this->max_data_bit) {
			$this->addData(0, $this->max_data_bit - $this->data_bits);
		} elseif ($this->data_bits > $this->max_data_bit) {
			throw new \Mpdf\QrCode\QrCodeException('QrCode data overflow error');
		}

		// construction des mots de 8 bit
		$this->data_word = [];
		$this->data_word[0] = 0;
		$nb_word = 0;

		$remaining_bit = 8;
		for ($i = 0; $i < $this->data_cur; $i++) {
			$buffer_val = $this->data_val[$i];
			$buffer_bit = $this->data_bit[$i];

			$flag = true;
			while ($flag) {
				if ($remaining_bit > $buffer_bit) {
					$this->data_word[$nb_word] = ((@$this->data_word[$nb_word] << $buffer_bit) | $buffer_val);
					$remaining_bit -= $buffer_bit;
					$flag = false;
				} else {
					$buffer_bit -= $remaining_bit;
					$this->data_word[$nb_word] = ((@$this->data_word[$nb_word] << $remaining_bit) | ($buffer_val >> $buffer_bit));
					$nb_word++;

					if ($buffer_bit == 0) {
						$flag = false;
					} else {
						$buffer_val = ($buffer_val & ((1 << $buffer_bit) - 1));
					}

					if ($nb_word < $this->max_data_word - 1) {
						$this->data_word[$nb_word] = 0;
					}
					$remaining_bit = 8;
				}
			}
		}

		// completion du dernier mot si incomplet
		if ($remaining_bit < 8) {
			$this->data_word[$nb_word] = $this->data_word[$nb_word] << $remaining_bit;
		} else {
			$nb_word--;
		}

		// remplissage du reste
		if ($nb_word < $this->max_data_word - 1) {
			$flag = true;
			while ($nb_word < $this->max_data_word - 1) {
				$nb_word++;
				if ($flag) {
					$this->data_word[$nb_word] = 236;
				} else {
					$this->data_word[$nb_word] = 17;
				}
				$flag = !$flag;
			}
		}
	}

	private function loadECC()
	{
		$matrix_remain_bit = [0, 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0];
		$this->matrix_remain = $matrix_remain_bit[$this->version];
		unset($matrix_remain_bit);

		// lecture du fichier : data file of geometry & mask for version V ,ecc level N
		$this->byte_num = $this->matrix_remain + 8 * $this->max_word;
		$filename = __DIR__ . "/data/qrv" . $this->version . "_" . $this->ec . ".dat";
		$fp1 = fopen($filename, "rb");
		$this->matrix_x_array = unpack("C*", fread($fp1, $this->byte_num));
		$this->matrix_y_array = unpack("C*", fread($fp1, $this->byte_num));
		$this->mask_array = unpack("C*", fread($fp1, $this->byte_num));
		$this->format_information_x2 = unpack("C*", fread($fp1, 15));
		$this->format_information_y2 = unpack("C*", fread($fp1, 15));
		$this->rs_ecc_codewords = ord(fread($fp1, 1));
		$this->rs_block_order = unpack("C*", fread($fp1, 128));
		fclose($fp1);
		$this->format_information_x1 = [0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8];
		$this->format_information_y1 = [8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0];
	}

	private function makeECC()
	{
		// lecture du fichier : data file of caluclatin tables for RS encoding
		$rs_cal_table_array = [];
		$filename = __DIR__ . "/data/rsc" . $this->rs_ecc_codewords . ".dat";
		$fp0 = fopen($filename, "rb");
		for ($i = 0; $i < 256; $i++) {
			$rs_cal_table_array[$i] = fread($fp0, $this->rs_ecc_codewords);
		}
		fclose($fp0);

		$max_data_codewords = count($this->data_word);

		// preparation
		$j = 0;
		$rs_block_number = 0;
		$rs_temp[0] = "";
		for ($i = 0; $i < $max_data_codewords; $i++) {
			$rs_temp[$rs_block_number] .= chr($this->data_word[$i]);
			$j++;
			if ($j >= $this->rs_block_order[$rs_block_number + 1] - $this->rs_ecc_codewords) {
				$j = 0;
				$rs_block_number++;
				$rs_temp[$rs_block_number] = "";
			}
		}

		// make
		$rs_block_order_num = count($this->rs_block_order);

		for ($rs_block_number = 0; $rs_block_number < $rs_block_order_num; $rs_block_number++) {
			$rs_codewords = $this->rs_block_order[$rs_block_number + 1];
			$rs_data_codewords = $rs_codewords - $this->rs_ecc_codewords;

			$rstemp = $rs_temp[$rs_block_number] . str_repeat(chr(0), $this->rs_ecc_codewords);
			$padding_data = str_repeat(chr(0), $rs_data_codewords);

			$j = $rs_data_codewords;
			while ($j > 0) {
				$first = ord(substr($rstemp, 0, 1));

				if ($first) {
					$left_chr = substr($rstemp, 1);
					$cal = $rs_cal_table_array[$first] . $padding_data;
					$rstemp = $left_chr ^ $cal;
				} else {
					$rstemp = substr($rstemp, 1);
				}
				$j--;
			}

			$this->data_word = array_merge($this->data_word, unpack("C*", $rstemp));
		}
	}

	private function makeMatrix()
	{
		// preparation
		$this->matrix = array_fill(0, $this->size, array_fill(0, $this->size, 0));

		// mettre les words
		for ($i = 0; $i < $this->max_word; $i++) {
			$word = $this->data_word[$i];
			for ($j = 8; $j > 0; $j--) {
				$bit_pos = ($i << 3) + $j;
				$this->matrix[$this->matrix_x_array[$bit_pos]][$this->matrix_y_array[$bit_pos]] = ((255 * ($word & 1)) ^ $this->mask_array[$bit_pos]);
				$word = $word >> 1;
			}
		}

		for ($k = $this->matrix_remain; $k > 0; $k--) {
			$bit_pos = $k + ($this->max_word << 3);
			$this->matrix[$this->matrix_x_array[$bit_pos]][$this->matrix_y_array[$bit_pos]] = (255 ^ $this->mask_array[$bit_pos]);
		}

		// mask select
		$min_demerit_score = 0;
		$hor_master = "";
		$ver_master = "";
		$k = 0;
		while ($k < $this->size) {
			$l = 0;
			while ($l < $this->size) {
				$hor_master = $hor_master . chr($this->matrix[$l][$k]);
				$ver_master = $ver_master . chr($this->matrix[$k][$l]);
				$l++;
			}
			$k++;
		}

		$i = 0;
		$all_matrix = $this->size * $this->size;

		while ($i < 8) {
			$demerit_n1 = 0;
			$ptn_temp = [];
			$bit = 1 << $i;
			$bit_r = (~$bit) & 255;
			$bit_mask = str_repeat(chr($bit), $all_matrix);
			$hor = $hor_master & $bit_mask;
			$ver = $ver_master & $bit_mask;

			$ver_shift1 = $ver . str_repeat(chr(170), $this->size);
			$ver_shift2 = str_repeat(chr(170), $this->size) . $ver;
			$ver_shift1_0 = $ver . str_repeat(chr(0), $this->size);
			$ver_shift2_0 = str_repeat(chr(0), $this->size) . $ver;
			$ver_or = chunk_split(~($ver_shift1 | $ver_shift2), $this->size, chr(170));
			$ver_and = chunk_split(~($ver_shift1_0 & $ver_shift2_0), $this->size, chr(170));

			$hor = chunk_split(~$hor, $this->size, chr(170));
			$ver = chunk_split(~$ver, $this->size, chr(170));
			$hor = $hor . chr(170) . $ver;

			$n1_search = "/" . str_repeat(chr(255), 5) . "+|" . str_repeat(chr($bit_r), 5) . "+/";
			$n3_search = chr($bit_r) . chr(255) . chr($bit_r) . chr($bit_r) . chr($bit_r) . chr(255) . chr($bit_r);

			$demerit_n3 = substr_count($hor, $n3_search) * 40;
			$demerit_n4 = floor(abs(((100 * (substr_count($ver, chr($bit_r)) / ($this->byte_num))) - 50) / 5)) * 10;

			$n2_search1 = "/" . chr($bit_r) . chr($bit_r) . "+/";
			$n2_search2 = "/" . chr(255) . chr(255) . "+/";
			$demerit_n2 = 0;
			preg_match_all($n2_search1, $ver_and, $ptn_temp);
			foreach ($ptn_temp[0] as $str_temp) {
				$demerit_n2 += (strlen($str_temp) - 1);
			}
			$ptn_temp = [];
			preg_match_all($n2_search2, $ver_or, $ptn_temp);
			foreach ($ptn_temp[0] as $str_temp) {
				$demerit_n2 += (strlen($str_temp) - 1);
			}
			$demerit_n2 *= 3;

			$ptn_temp = [];

			preg_match_all($n1_search, $hor, $ptn_temp);
			foreach ($ptn_temp[0] as $str_temp) {
				$demerit_n1 += (strlen($str_temp) - 2);
			}
			$demerit_score = $demerit_n1 + $demerit_n2 + $demerit_n3 + $demerit_n4;

			if ($demerit_score <= $min_demerit_score || $i == 0) {
				$mask_number = $i;
				$min_demerit_score = $demerit_score;
			}

			$i++;
		}

		$mask_content = 1 << $mask_number;

		$format_information_value = (($this->ec << 3) | $mask_number);
		$format_information_array = ["101010000010010", "101000100100101",
			"101111001111100", "101101101001011", "100010111111001", "100000011001110",
			"100111110010111", "100101010100000", "111011111000100", "111001011110011",
			"111110110101010", "111100010011101", "110011000101111", "110001100011000",
			"110110001000001", "110100101110110", "001011010001001", "001001110111110",
			"001110011100111", "001100111010000", "000011101100010", "000001001010101",
			"000110100001100", "000100000111011", "011010101011111", "011000001101000",
			"011111100110001", "011101000000110", "010010010110100", "010000110000011",
			"010111011011010", "010101111101101"];

		for ($i = 0; $i < 15; $i++) {
			$content = substr($format_information_array[$format_information_value], $i, 1);

			$this->matrix[$this->format_information_x1[$i]][$this->format_information_y1[$i]] = $content * 255;
			$this->matrix[$this->format_information_x2[$i + 1]][$this->format_information_y2[$i + 1]] = $content * 255;
		}

		$this->final = unpack("C*", file_get_contents(__DIR__ . '/data/modele' . $this->version . '.dat'));
		$this->qr_size = $this->size + 8;

		for ($x = 0; $x < $this->size; $x++) {
			for ($y = 0; $y < $this->size; $y++) {
				if ($this->matrix[$x][$y] & $mask_content) {
					$this->final[($x + 4) + ($y + 4) * $this->qr_size + 1] = true;
				}
			}
		}
	}
}
