1408 lines
41 KiB
PHP
1408 lines
41 KiB
PHP
<?php
|
|
|
|
/**
|
|
* QRCode - QR 2D barcode generator
|
|
*
|
|
* QRCode - QR 2D barcode generator
|
|
*
|
|
* PHP version 5
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this library in the file LICENSE.LGPL; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
* 02111-1307 USA
|
|
*
|
|
* From the original code by Y.Swetake:
|
|
* THIS SOFTWARE IS PROVIDED BY Y.Swetake ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL Y.Swetake OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
|
|
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
* @category Images
|
|
* @package Image_QRCode
|
|
* @author Y.Swetake <swe@venus.dti.ne.jp>
|
|
* @author Rich Sage <rich.sage@gmail.com>
|
|
* @copyright 2009 Y.Swetake
|
|
* @copyright 2009 R.Sage
|
|
* @license http://www.gnu.org/copyleft/lesser.html LGPL License 2.1
|
|
* @version SVN: $Id$
|
|
* @link http://code.google.com/p/pearqrcode/
|
|
*/
|
|
|
|
/**
|
|
* Exception handling
|
|
*/
|
|
|
|
require_once 'QRCode/Exception.php';
|
|
|
|
/**
|
|
* QRCode
|
|
*
|
|
* @category Images
|
|
* @package Image_QRCode
|
|
* @author Y.Swetake <swe@venus.dti.ne.jp>
|
|
* @author Rich Sage <rich.sage@gmail.com>
|
|
* @license http://www.gnu.org/licenses/lgpl-2.1.txt GNU LGPL
|
|
* @version Release: 0.1
|
|
* @link http://code.google.com/p/pearqrcode/
|
|
*/
|
|
|
|
class Image_QRCode
|
|
{
|
|
|
|
/**
|
|
* path to the data directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $path;
|
|
|
|
/**
|
|
* path to the image directory
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $image_path;
|
|
|
|
/**
|
|
* upper limit for version
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $version_ul;
|
|
|
|
/**
|
|
* string/data to encode
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $data_string;
|
|
|
|
/**
|
|
* output type
|
|
* - return: return raw PHP GD image object
|
|
* - display: output to browser
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $output_type;
|
|
|
|
/**
|
|
* image_type
|
|
* - png/jpeg
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $image_type;
|
|
|
|
/**
|
|
* base_image
|
|
*
|
|
* @var gd_image
|
|
*/
|
|
protected $base_image;
|
|
|
|
/**
|
|
* output image
|
|
*
|
|
* @var gd_image
|
|
*/
|
|
protected $output_image;
|
|
|
|
/**
|
|
* total number of data bits
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $total_data_bits;
|
|
|
|
/**
|
|
* Error correction indicator
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $error_correct;
|
|
|
|
/**
|
|
* lookup table for alphanumeric characters
|
|
* Note: the '$' index is in single quotes as otherwise
|
|
* PHP tries to parse it as a variable (see PEAR bug #17321)
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $alphanumeric_hash = array(
|
|
"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
|
|
);
|
|
|
|
/**
|
|
* Maximum data bits lookup
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $max_data_bits_array = array(
|
|
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
|
|
);
|
|
|
|
/**
|
|
* Maximum number of codewords, dependant on barcode version
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $max_codewords_array = 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
|
|
);
|
|
|
|
/**
|
|
* Formatting data for the final barcode
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $format_array = 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"
|
|
);
|
|
|
|
/**
|
|
* Maximum number of data bits
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $max_data_bits;
|
|
|
|
/**
|
|
* Size of the module
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $module_size;
|
|
|
|
/**
|
|
* Data values
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $data_value;
|
|
|
|
/**
|
|
* Incremental counter, pointer into the data arrays
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $data_counter;
|
|
|
|
/**
|
|
* Data bits array
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $data_bits;
|
|
|
|
/**
|
|
* Codeword pointer, incremental
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $codeword_num_counter_value;
|
|
|
|
/**
|
|
* Codeword details
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $codeword_num_plus;
|
|
|
|
/**
|
|
* RS-ECC codewords
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $rs_ecc_codewords;
|
|
|
|
/**
|
|
* Codewords calculated prior to matrix generation
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $codewords;
|
|
|
|
/**
|
|
* Maximum number of data codewords
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $max_data_codewords;
|
|
|
|
/**
|
|
* Final matrix details for plotting barcode
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $matrix_content;
|
|
|
|
/**
|
|
* Matrix X array
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $matrix_x_array;
|
|
|
|
/**
|
|
* Matrix Y array
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $matrix_y_array;
|
|
|
|
/**
|
|
* Masking array - used in final matrix generation
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $mask_array;
|
|
|
|
/**
|
|
* RS Block order array
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $rs_block_order;
|
|
|
|
/**
|
|
* RS Calculation table array
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $rs_cal_table_array;
|
|
|
|
/**
|
|
* Byte number counter
|
|
*
|
|
* @var integer
|
|
*/
|
|
protected $byte_num;
|
|
|
|
/**
|
|
* Class constructor
|
|
*
|
|
* @param array $options An array of options for the class
|
|
*/
|
|
public function __construct($options = array())
|
|
{
|
|
$this->path = "@data_dir@" . DIRECTORY_SEPARATOR . "Image_QRCode" .
|
|
DIRECTORY_SEPARATOR . "data";
|
|
if ("@data_dir@" == "@" . "data_dir@") {
|
|
// development path
|
|
$this->path = dirname(__FILE__) . "/../data";
|
|
}
|
|
|
|
$this->image_path = "@data_dir@" . DIRECTORY_SEPARATOR . "Image_QRCode" .
|
|
DIRECTORY_SEPARATOR . "image";
|
|
if ("@data_dir@" == "@" . "data_dir@") {
|
|
// development path
|
|
$this->image_path = dirname(__FILE__) . "/../image";
|
|
}
|
|
|
|
$this->version_ul = 40;
|
|
|
|
$this->data_string = "";
|
|
|
|
$this->output_type = "display";
|
|
$this->image_type = "png";
|
|
|
|
$this->total_data_bits = 0;
|
|
$this->error_correct = "M";
|
|
$this->module_size = 0;
|
|
|
|
$this->matrix_content = array();
|
|
|
|
// handle options
|
|
if (array_key_exists("version", $options)) {
|
|
$this->setVersion($options["version"]);
|
|
}
|
|
if (array_key_exists("path", $options)) {
|
|
$this->path = $options["path"];
|
|
}
|
|
if (array_key_exists("image_path", $options)) {
|
|
$this->image_path = $options["image_path"];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the data to encode
|
|
*
|
|
* @param string $str the data to encode into the barcode
|
|
*
|
|
* @return void
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
protected function setData($str)
|
|
{
|
|
$str = trim($str);
|
|
|
|
if (strlen($str) == 0) {
|
|
throw new Image_QRCode_Exception("Data cannot be empty");
|
|
}
|
|
$this->data_string = $str;
|
|
$this->data_length = strlen($str);
|
|
}
|
|
|
|
/**
|
|
* Sets the error correction level.
|
|
* One of:
|
|
* L: 7% error level
|
|
* M: 15% error level
|
|
* Q: 25% error level
|
|
* H: 30% error level
|
|
*
|
|
* @param char $e the error level to use
|
|
*
|
|
* @return void
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
protected function setErrorCorrect($e)
|
|
{
|
|
$e = strtoupper(trim($e));
|
|
if (!in_array($e, array("L", "M", "Q", "H"))) {
|
|
throw new Image_QRCode_Exception("Error correction level not supported");
|
|
}
|
|
$this->error_correct = $e;
|
|
}
|
|
|
|
/**
|
|
* Sets the default module size
|
|
* Defaults are PNG: 4, JPEG: 8
|
|
*
|
|
* @param integer $size the module size to use
|
|
*
|
|
* @return void
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
protected function setModuleSize($size)
|
|
{
|
|
$size = intval($size);
|
|
if ($size < 1) {
|
|
throw new Image_QRCode_Exception("Module size is invalid");
|
|
}
|
|
$this->module_size = $size;
|
|
}
|
|
|
|
/**
|
|
* Sets the version number to use
|
|
* (between 1 and 40)
|
|
*
|
|
* Version 1 is 21*21 matrix
|
|
* and 4 modules increases whenever 1 version increases.
|
|
* So version 40 is 177*177 matrix.
|
|
*
|
|
* @param integer $v the version number to use for this barcode
|
|
*
|
|
* @return void
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
protected function setVersion($v)
|
|
{
|
|
$v = intval($v);
|
|
if ($v < 1 || $v > $this->version_ul) {
|
|
throw new Image_QRCode_Exception("Version is invalid");
|
|
}
|
|
$this->version = $v;
|
|
}
|
|
|
|
/**
|
|
* Defines the return image type
|
|
* (from 'jpeg', or 'png')
|
|
*
|
|
* Default is PNG format
|
|
*
|
|
* @param string $t the image type to use
|
|
*
|
|
* @return void
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
protected function setImageType($t)
|
|
{
|
|
$t = strtolower($t);
|
|
if (!in_array($t, array("jpeg", "png"))) {
|
|
throw new Image_QRCode_Exception("Image format not supported");
|
|
}
|
|
$this->image_type = $t;
|
|
}
|
|
|
|
/**
|
|
* Sets what to do once the code has been generated
|
|
* ('display' or 'return')
|
|
*
|
|
* Default is to display the image, complete with headers.
|
|
*
|
|
* @param string $t what to do with the image once it's been created
|
|
*
|
|
* @return void
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
protected function setOutputType($t)
|
|
{
|
|
$t = strtolower($t);
|
|
if (!in_array($t, array("display", "return"))) {
|
|
throw new Image_QRCode_Exception("Output type not supported");
|
|
}
|
|
$this->output_type = $t;
|
|
}
|
|
|
|
/**
|
|
* Sets the data to append to the code (experimental)
|
|
*
|
|
* @param array $data an array of elements (n, m, parity, originaldata)
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function setStructureAppend($data = array())
|
|
{
|
|
if (!array_key_exists("n", $data)
|
|
&& !array_key_exists("m", $data)
|
|
&& !array_key_exists("parity", $data)
|
|
&& !array_key_exists("originaldata", $data)
|
|
) {
|
|
throw new Image_QRCode_Exception("Appended structure data is not valid");
|
|
}
|
|
|
|
$this->structureappend_n = $data["n"];
|
|
$this->structureappend_m = $data["m"];
|
|
$this->structureappend_parity = $data["parity"];
|
|
$this->structureappend_originaldata = $data["originaldata"];
|
|
}
|
|
|
|
/**
|
|
* Performs all necessary calculations and returns/outputs an image
|
|
* as defined by the configuration
|
|
*
|
|
* @param string $data the string/data to encode into the barcode
|
|
* @param array $options an array of options for the barcode
|
|
* @param array $append additional data to append (experimental)
|
|
*
|
|
* @return resource
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
public function makeCode($data = "", $options = array(), $append = array())
|
|
{
|
|
// apply any passed options
|
|
if (array_key_exists("image_type", $options)) {
|
|
$this->setImageType($options["image_type"]);
|
|
}
|
|
if (array_key_exists("output_type", $options)) {
|
|
$this->setOutputType($options["output_type"]);
|
|
}
|
|
if (array_key_exists("error_correct", $options)) {
|
|
$this->setErrorCorrect($options["error_correct"]);
|
|
}
|
|
if (array_key_exists("module_size", $options)) {
|
|
$this->setModuleSize($options["module_size"]);
|
|
}
|
|
|
|
// set our data value
|
|
$this->setData($data);
|
|
|
|
// if we have an extra set of data to append, set this now
|
|
if (!empty($append)) {
|
|
$this->setStructureAppend($append);
|
|
}
|
|
|
|
// some checks to start with
|
|
if ($this->module_size <= 0) {
|
|
$this->module_size = 4;
|
|
}
|
|
|
|
$this->data_counter = 0;
|
|
|
|
// apply parity and m/n structure append data
|
|
$this->applyStructureAppend();
|
|
|
|
$this->data_bits[$this->data_counter] = 4;
|
|
|
|
// Determine the encoding mode, based on the input data
|
|
$this->determineEncoding();
|
|
|
|
if (@$this->data_bits[$this->data_counter] > 0) {
|
|
$this->data_counter++;
|
|
}
|
|
$i = 0;
|
|
$this->total_data_bits = 0;
|
|
while ($i < $this->data_counter) {
|
|
$this->total_data_bits += $this->data_bits[$i];
|
|
$i++;
|
|
}
|
|
|
|
$ecc_character_hash = array(
|
|
"L"=>"1",
|
|
"l"=>"1",
|
|
"M"=>"0",
|
|
"m"=>"0",
|
|
"Q"=>"3",
|
|
"q"=>"3",
|
|
"H"=>"2",
|
|
"h"=>"2"
|
|
);
|
|
|
|
$this->ec = @$ecc_character_hash[$this->error_correct];
|
|
|
|
if (!$this->ec) {
|
|
$this->ec = 0;
|
|
}
|
|
|
|
// Calculate the version of the code we're generating
|
|
$this->checkVersion();
|
|
|
|
$this->total_data_bits += $this->codeword_num_plus[$this->version];
|
|
$this->data_bits[$this->codeword_num_counter_value]
|
|
+= $this->codeword_num_plus[$this->version];
|
|
|
|
$max_codewords = $this->max_codewords_array[$this->version];
|
|
$max_modules_1side = 17 + ($this->version << 2);
|
|
|
|
$matrix_remain_bit = array(
|
|
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
|
|
);
|
|
|
|
/* Create our base clean matrix data, ready for our own data */
|
|
$this->emptyMatrix($max_modules_1side);
|
|
|
|
/* ECC -this is the heart of the codeword calculations */
|
|
$format_info = $this->performECCOperation(
|
|
$matrix_remain_bit,
|
|
$max_codewords
|
|
);
|
|
|
|
/* Attach calculated codeword data to the matrix */
|
|
$this->attachCodewordData($max_codewords, $matrix_remain_bit);
|
|
|
|
/* Mask selection */
|
|
$mask_number = $this->maskSelection($max_modules_1side);
|
|
$mask_content = 1 << $mask_number;
|
|
|
|
/* Calculate our format information */
|
|
$this->calculateFormatInformation($format_info, $mask_number);
|
|
|
|
/* Create base image based on calculated size */
|
|
$mib = $this->createBaseImage($max_modules_1side);
|
|
|
|
// Add actual matrix data to the base image
|
|
$this->addMatrixToImage($max_modules_1side, $mask_content);
|
|
|
|
// create output image, saved in $this->output_image
|
|
$this->createOutputImage($mib);
|
|
|
|
// what are we doing with the created output image?
|
|
if ($this->output_type == "return") {
|
|
return $this->output_image;
|
|
} else {
|
|
switch ($this->image_type)
|
|
{
|
|
case "jpeg":
|
|
header("Content-type: image/jpeg");
|
|
imagejpeg($this->output_image);
|
|
break;
|
|
|
|
case "png":
|
|
header("Content-type: image/png");
|
|
imagepng($this->output_image);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs version checking/calculation
|
|
*
|
|
* @return void
|
|
* @throws Image_QRCode_Exception
|
|
*/
|
|
protected function checkVersion()
|
|
{
|
|
if (!is_numeric($this->version)) {
|
|
$this->version = 0;
|
|
}
|
|
|
|
if (!$this->version) {
|
|
$i = 1 + 40 * $this->ec;
|
|
$j = $i + 39;
|
|
$this->version = 1;
|
|
while ($i <= $j) {
|
|
$cw = $this->codeword_num_plus[$this->version];
|
|
|
|
$max = $this->max_data_bits_array[$i];
|
|
$tdb = $this->total_data_bits + $cw;
|
|
|
|
if ($max >= $tdb) {
|
|
$this->max_data_bits = $this->max_data_bits_array[$i];
|
|
break;
|
|
}
|
|
$i++;
|
|
$this->version++;
|
|
}
|
|
} else {
|
|
$b = $this->max_data_bits_array[$this->version + 40 * $this->ec];
|
|
$this->max_data_bits = $b;
|
|
}
|
|
if ($this->version > $this->version_ul) {
|
|
throw new Image_QRCode_Exception("Version number is too large");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determines the encoding needed for the data provided
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function determineEncoding()
|
|
{
|
|
if (preg_match("/[^0-9]/", $this->data_string) != 0) {
|
|
|
|
$expr = "/[^0-9A-Z \$\*\%\+\.\/\:\-]/";
|
|
if (preg_match($expr, $this->data_string) != 0) {
|
|
|
|
/* 8-bit byte mode */
|
|
|
|
$this->codeword_num_plus = array(
|
|
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
|
|
);
|
|
|
|
$this->data_value[$this->data_counter] = 4;
|
|
$this->data_counter++;
|
|
$this->data_value[$this->data_counter] = $this->data_length;
|
|
$this->data_bits[$this->data_counter] = 8; /* version 1-9 */
|
|
$this->codeword_num_counter_value = $this->data_counter;
|
|
|
|
$this->data_counter++;
|
|
$i = 0;
|
|
while ($i < $this->data_length) {
|
|
$this->data_value[$this->data_counter] = ord(
|
|
substr(
|
|
$this->data_string,
|
|
$i,
|
|
1
|
|
)
|
|
);
|
|
$this->data_bits[$this->data_counter] = 8;
|
|
$this->data_counter++;
|
|
$i++;
|
|
}
|
|
} else {
|
|
|
|
/* Alphanumeric mode */
|
|
|
|
$this->codeword_num_plus = array(
|
|
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
|
|
);
|
|
|
|
$this->data_value[$this->data_counter] = 2;
|
|
$this->data_counter++;
|
|
$this->data_value[$this->data_counter] = $this->data_length;
|
|
$this->data_bits[$this->data_counter] = 9; /* version 1-9 */
|
|
$this->codeword_num_counter_value = $this->data_counter;
|
|
|
|
$i = 0;
|
|
$this->data_counter++;
|
|
while ($i < $this->data_length) {
|
|
if (($i % 2) == 0) {
|
|
$c = substr($this->data_string, $i, 1);
|
|
$h = $this->alphanumeric_hash[$c];
|
|
$this->data_value[$this->data_counter] = $h;
|
|
$this->data_bits[$this->data_counter] = 6;
|
|
} else {
|
|
$c = substr($this->data_string, $i, 1);
|
|
$h = $this->alphanumeric_hash[$c];
|
|
|
|
$this->data_value[$this->data_counter]
|
|
= $this->data_value[$this->data_counter] * 45 + $h;
|
|
$this->data_bits[$this->data_counter] = 11;
|
|
$this->data_counter++;
|
|
}
|
|
$i++;
|
|
}
|
|
}
|
|
} else {
|
|
|
|
/* Numeric mode */
|
|
|
|
$this->codeword_num_plus = array(
|
|
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
|
|
);
|
|
|
|
$this->data_value[$this->data_counter] = 1;
|
|
$this->data_counter++;
|
|
$this->data_value[$this->data_counter] = $this->data_length;
|
|
$this->data_bits[$this->data_counter] = 10; /* #version 1-9 */
|
|
$this->codeword_num_counter_value = $this->data_counter;
|
|
|
|
$i = 0;
|
|
$this->data_counter++;
|
|
while ($i < $this->data_length) {
|
|
if (($i % 3) == 0) {
|
|
$this->data_value[$this->data_counter]
|
|
= substr($this->data_string, $i, 1);
|
|
$this->data_bits[$this->data_counter] = 4;
|
|
} else {
|
|
$c = substr($this->data_string, $i, 1);
|
|
$this->data_value[$this->data_counter]
|
|
= $this->data_value[$this->data_counter] * 10 + $c;
|
|
|
|
if (($i % 3) == 1) {
|
|
$this->data_bits[$this->data_counter] = 7;
|
|
} else {
|
|
$this->data_bits[$this->data_counter] = 10;
|
|
$this->data_counter++;
|
|
}
|
|
}
|
|
$i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Performs the error correction and code generation operations
|
|
*
|
|
* @param array $matrix_remain_bit array of remaining bits for data matrix
|
|
* @param integer $max_codewords number of maximum codewords
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function performECCOperation($matrix_remain_bit, $max_codewords)
|
|
{
|
|
// get our ECC data in from the relevant file
|
|
$format_info = $this->readECCData($matrix_remain_bit, $max_codewords);
|
|
|
|
$codewordsCounter = $this->divideBy8Bit();
|
|
$codewordsCounter = $this->setPaddingCharacter($codewordsCounter);
|
|
|
|
// Do the actual RS-ECC magic
|
|
$this->doRSCalculations();
|
|
|
|
return $format_info; // used in main makeCode()
|
|
}
|
|
|
|
/**
|
|
* Read in correct baseline data from ECC/RS files
|
|
*
|
|
* @param array $matrix_remain_bit array of remaining bits for data matrix
|
|
* @param integer $max_codewords number of maximum codewords
|
|
*
|
|
* @return array
|
|
*/
|
|
protected function readECCData($matrix_remain_bit, $max_codewords)
|
|
{
|
|
$this->byte_num = $matrix_remain_bit[$this->version]
|
|
+ ($max_codewords << 3);
|
|
$filename = $this->path
|
|
. "/qrv"
|
|
. $this->version
|
|
. "_"
|
|
. $this->ec
|
|
. ".dat";
|
|
if (!file_exists($filename)) {
|
|
throw new Image_QRCode_Exception("Can't open ECC data file");
|
|
}
|
|
$fp1 = fopen($filename, "rb");
|
|
|
|
$matx = fread($fp1, $this->byte_num);
|
|
$maty = fread($fp1, $this->byte_num);
|
|
$masks = fread($fp1, $this->byte_num);
|
|
$fi_x = fread($fp1, 15);
|
|
$fi_y = fread($fp1, 15);
|
|
$this->rs_ecc_codewords = ord(fread($fp1, 1));
|
|
$rso = fread($fp1, 128);
|
|
fclose($fp1);
|
|
|
|
$this->matrix_x_array = unpack("C*", $matx);
|
|
$this->matrix_y_array = unpack("C*", $maty);
|
|
$this->mask_array = unpack("C*", $masks);
|
|
|
|
$this->rs_block_order = unpack("C*", $rso);
|
|
|
|
$format_information_x2 = unpack("C*", $fi_x);
|
|
$format_information_y2 = unpack("C*", $fi_y);
|
|
|
|
$format_information_x1 = array(0,1,2,3,4,5,7,8,8,8,8,8,8,8,8);
|
|
$format_information_y1 = array(8,8,8,8,8,8,8,8,7,5,4,3,2,1,0);
|
|
|
|
$format_info = array(
|
|
"x1" => $format_information_x1,
|
|
"x2" => $format_information_x2,
|
|
"y1" => $format_information_y1,
|
|
"y2" => $format_information_y2
|
|
);
|
|
|
|
$this->max_data_codewords = ($this->max_data_bits >> 3);
|
|
|
|
$filename = $this->path . "/rsc" . $this->rs_ecc_codewords . ".dat";
|
|
if (!file_exists($filename)) {
|
|
throw new Image_QRCode_Exception("Can't open rsc data file");
|
|
}
|
|
$fp0 = fopen($filename, "rb");
|
|
|
|
$i = 0;
|
|
while ($i < 256) {
|
|
$this->rs_cal_table_array[$i] = fread($fp0, $this->rs_ecc_codewords);
|
|
$i++;
|
|
}
|
|
fclose($fp0);
|
|
|
|
/* Set terminator for data */
|
|
|
|
if ($this->total_data_bits <= $this->max_data_bits-4) {
|
|
$this->data_value[$this->data_counter] = 0;
|
|
$this->data_bits[$this->data_counter] = 4;
|
|
} else {
|
|
if ($this->total_data_bits < $this->max_data_bits) {
|
|
$this->data_value[$this->data_counter] = 0;
|
|
$this->data_bits[$this->data_counter]
|
|
= $this->max_data_bits-$this->total_data_bits;
|
|
} else {
|
|
if ($this->total_data_bits > $this->max_data_bits) {
|
|
throw new Image_QRCode_Exception("Overflow exception");
|
|
}
|
|
}
|
|
}
|
|
|
|
return $format_info;
|
|
}
|
|
|
|
/**
|
|
* Gets data into 8-bit format
|
|
*
|
|
* @return integer
|
|
*/
|
|
protected function divideBy8Bit()
|
|
{
|
|
$i = 0;
|
|
$codewords_counter = 0;
|
|
$this->codewords[0] = 0;
|
|
$remaining_bits = 8;
|
|
|
|
while ($i <= $this->data_counter) {
|
|
|
|
$buffer = @$this->data_value[$i];
|
|
$buffer_bits = @$this->data_bits[$i];
|
|
|
|
$flag = 1;
|
|
while ($flag) {
|
|
|
|
if ($remaining_bits > $buffer_bits) {
|
|
$this->codewords[$codewords_counter]
|
|
= ((@$this->codewords[$codewords_counter] << $buffer_bits)
|
|
| $buffer);
|
|
$remaining_bits -= $buffer_bits;
|
|
$flag = 0;
|
|
} else {
|
|
$buffer_bits -= $remaining_bits;
|
|
$this->codewords[$codewords_counter]
|
|
= (($this->codewords[$codewords_counter] << $remaining_bits)
|
|
| ($buffer >> $buffer_bits));
|
|
|
|
if ($buffer_bits == 0) {
|
|
$flag = 0;
|
|
} else {
|
|
$buffer = ($buffer & ((1 << $buffer_bits)-1));
|
|
$flag = 1;
|
|
}
|
|
|
|
$codewords_counter++;
|
|
if ($codewords_counter < $this->max_data_codewords-1) {
|
|
$this->codewords[$codewords_counter] = 0;
|
|
}
|
|
$remaining_bits = 8;
|
|
}
|
|
}
|
|
$i++;
|
|
}
|
|
|
|
if ($remaining_bits != 8) {
|
|
$this->codewords[$codewords_counter]
|
|
= $this->codewords[$codewords_counter] << $remaining_bits;
|
|
} else {
|
|
$codewords_counter--;
|
|
}
|
|
|
|
return $codewords_counter;
|
|
}
|
|
|
|
/**
|
|
* Sets the padding character to pad out data
|
|
*
|
|
* @param integer $codewordsCounter counter for current no. of codewords
|
|
*
|
|
* @return integer
|
|
*/
|
|
protected function setPaddingCharacter($codewordsCounter)
|
|
{
|
|
if ($codewordsCounter < $this->max_data_codewords-1) {
|
|
$flag = 1;
|
|
while ($codewordsCounter < $this->max_data_codewords-1) {
|
|
|
|
$codewordsCounter++;
|
|
if ($flag == 1) {
|
|
$this->codewords[$codewordsCounter] = 236;
|
|
} else {
|
|
$this->codewords[$codewordsCounter] = 17;
|
|
}
|
|
$flag = $flag * -1;
|
|
}
|
|
}
|
|
return $codewordsCounter;
|
|
}
|
|
|
|
/**
|
|
* Calculates the format information for the barcode
|
|
*
|
|
* @param array $format_info format information from data files
|
|
* @param integer $mask_number mask integer to use
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function calculateFormatInformation($format_info, $mask_number)
|
|
{
|
|
$format_value = (($this->ec << 3) | $mask_number);
|
|
$i = 0;
|
|
while ($i < 15) {
|
|
$content = substr($this->format_array[$format_value], $i, 1);
|
|
|
|
$x = $format_info["x1"][$i];
|
|
$y = $format_info["y1"][$i];
|
|
$this->matrix_content[$x][$y] = $content * 255;
|
|
|
|
$x = $format_info["x2"][$i+1];
|
|
$y = $format_info["y2"][$i+1];
|
|
$this->matrix_content[$x][$y] = $content * 255;
|
|
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Perform the actual RS calculations on the data
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function doRSCalculations()
|
|
{
|
|
// Preparation
|
|
$i = 0;
|
|
$j = 0;
|
|
$rs_block_number = 0;
|
|
$rs_temp[0] = "";
|
|
|
|
while ($i < $this->max_data_codewords) {
|
|
$rs_temp[$rs_block_number] .= chr($this->codewords[$i]);
|
|
$j++;
|
|
|
|
$v = $this->rs_block_order[$rs_block_number+1]
|
|
- $this->rs_ecc_codewords;
|
|
if ($j >= $v) {
|
|
$j = 0;
|
|
$rs_block_number++;
|
|
$rs_temp[$rs_block_number] = "";
|
|
}
|
|
$i++;
|
|
}
|
|
|
|
// RS-ECC main calculation
|
|
$rs_block_number = 0;
|
|
$this->rs_block_order_num = count($this->rs_block_order);
|
|
|
|
while ($rs_block_number < $this->rs_block_order_num) {
|
|
$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 = $this->rs_cal_table_array[$first] . $padding_data;
|
|
$rstemp = $left_chr ^ $cal;
|
|
} else {
|
|
$rstemp=substr($rstemp, 1);
|
|
}
|
|
|
|
$j--;
|
|
}
|
|
|
|
$this->codewords = array_merge(
|
|
$this->codewords,
|
|
unpack("C*", $rstemp)
|
|
);
|
|
|
|
$rs_block_number++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the final GD output image block
|
|
* ready for sending wherever the user desires
|
|
*
|
|
* @param integer $mib destination image width/height
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function createOutputImage($mib)
|
|
{
|
|
imagecopyresized(
|
|
$this->output_image,
|
|
$this->base_image,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
$this->image_size,
|
|
$this->image_size,
|
|
$mib,
|
|
$mib
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Adds m/n and parity data for structure append (if supplied)
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function applyStructureAppend()
|
|
{
|
|
if ($this->structureappend_n > 1
|
|
&& $this->structureappend_n <= 16
|
|
&& $this->structureappend_m > 0
|
|
&& $this->structureappend_m <= 16
|
|
) {
|
|
|
|
$this->data_value[0] = 3;
|
|
$this->data_bits[0] = 4;
|
|
|
|
$this->data_value[1] = $this->structureappend_m - 1;
|
|
$this->data_bits[1] = 4;
|
|
|
|
$this->data_value[2] = $this->structureappend_n - 1;
|
|
$this->data_bits[2] = 4;
|
|
|
|
|
|
$originaldata_length = strlen($this->structureappend_originaldata);
|
|
if ($originaldata_length > 1) {
|
|
$this->structureappend_parity = 0;
|
|
$i = 0;
|
|
while ($i < $originaldata_length) {
|
|
$ord = ord(
|
|
substr(
|
|
$this->structureappend_originaldata,
|
|
$i,
|
|
1
|
|
)
|
|
);
|
|
$this->structureappend_parity
|
|
= $this->structureappend_parity ^ $ord;
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
$this->data_value[3] = $this->structureappend_parity;
|
|
$this->data_bits[3] = 8;
|
|
|
|
$this->data_counter = 4;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attaches the calculated codeword data to the data matrix
|
|
*
|
|
* @param integer $max_codewords Maximum number of codewords for this code
|
|
* @param integer $matrix_remain_bit Dependant on the version of the code
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function attachCodewordData($max_codewords, $matrix_remain_bit)
|
|
{
|
|
$i = 0;
|
|
while ($i < $max_codewords) {
|
|
$codeword_i=$this->codewords[$i];
|
|
$j = 8;
|
|
while ($j >= 1) {
|
|
$codeword_bits_number = ($i << 3) + $j;
|
|
|
|
$x = $this->matrix_x_array[$codeword_bits_number];
|
|
$y = $this->matrix_y_array[$codeword_bits_number];
|
|
|
|
$this->matrix_content[$x][$y]
|
|
= ((255*($codeword_i & 1))
|
|
^ $this->mask_array[$codeword_bits_number]);
|
|
|
|
$codeword_i= $codeword_i >> 1;
|
|
$j--;
|
|
}
|
|
$i++;
|
|
}
|
|
|
|
$matrix_remain = $matrix_remain_bit[$this->version];
|
|
while ($matrix_remain) {
|
|
$remain_bit_temp = $matrix_remain + ( $max_codewords << 3);
|
|
|
|
$x = $this->matrix_x_array[$remain_bit_temp];
|
|
$y = $this->matrix_y_array[$remain_bit_temp];
|
|
|
|
$this->matrix_content[$x][$y]
|
|
= (255 ^ $this->mask_array[$remain_bit_temp]);
|
|
$matrix_remain--;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Selects the mask to use
|
|
*
|
|
* @param integer $max_modules_1side Max number of modules (single side)
|
|
*
|
|
* @return integer
|
|
*/
|
|
protected function maskSelection($max_modules_1side)
|
|
{
|
|
$min_demerit_score = 0;
|
|
$hor_master = "";
|
|
$ver_master = "";
|
|
$k = 0;
|
|
while ($k < $max_modules_1side) {
|
|
$l = 0;
|
|
while ($l < $max_modules_1side) {
|
|
$hor_master = $hor_master . chr($this->matrix_content[$l][$k]);
|
|
$ver_master = $ver_master . chr($this->matrix_content[$k][$l]);
|
|
$l++;
|
|
}
|
|
$k++;
|
|
}
|
|
$i = 0;
|
|
$all_matrix = $max_modules_1side * $max_modules_1side;
|
|
while ($i < 8) {
|
|
$demerit_n1 = 0;
|
|
$ptn_temp = array();
|
|
$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), $max_modules_1side);
|
|
$ver_shift2 = str_repeat(chr(170), $max_modules_1side) . $ver;
|
|
$ver_shift1_0 = $ver . str_repeat(chr(0), $max_modules_1side);
|
|
$ver_shift2_0 = str_repeat(chr(0), $max_modules_1side) . $ver;
|
|
$ver_or = chunk_split(
|
|
~($ver_shift1 | $ver_shift2),
|
|
$max_modules_1side, chr(170)
|
|
);
|
|
$ver_and = chunk_split(
|
|
~($ver_shift1_0 & $ver_shift2_0),
|
|
$max_modules_1side, chr(170)
|
|
);
|
|
|
|
$hor = chunk_split(~$hor, $max_modules_1side, chr(170));
|
|
$ver = chunk_split(~$ver, $max_modules_1side, 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;
|
|
|
|
$sc = substr_count($ver, chr($bit_r));
|
|
$demerit_n4 = floor(abs(((100 * ($sc / ($this->byte_num))) - 50) / 5));
|
|
$demerit_n4 *= 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 = array();
|
|
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=array();
|
|
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_score += $demerit_n3 + $demerit_n4;
|
|
|
|
if ($demerit_score <= $min_demerit_score || $i == 0) {
|
|
$mask_number = $i;
|
|
$min_demerit_score = $demerit_score;
|
|
}
|
|
|
|
$i++;
|
|
}
|
|
|
|
return $mask_number;
|
|
}
|
|
|
|
/**
|
|
* Clears the output matrix array, ready for new data
|
|
*
|
|
* @param integer $max_modules_1side Max number of modules (single side)
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function emptyMatrix($max_modules_1side)
|
|
{
|
|
$i = 0;
|
|
while ($i < $max_modules_1side) {
|
|
$j = 0;
|
|
while ($j < $max_modules_1side) {
|
|
$this->matrix_content[$j][$i] = 0;
|
|
$j++;
|
|
}
|
|
$i++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the base square image, based on the module size
|
|
*
|
|
* @param integer $max_modules_1side Max number of modules (single side)
|
|
*
|
|
* @return integer
|
|
*/
|
|
protected function createBaseImage($max_modules_1side)
|
|
{
|
|
$mib = $max_modules_1side+8;
|
|
$this->image_size = $mib * $this->module_size;
|
|
if ($this->image_size > 1480) {
|
|
throw new Image_QRCode_Exception("Image size is too large");
|
|
}
|
|
|
|
// image is square
|
|
$this->output_image = imagecreate(
|
|
$this->image_size,
|
|
$this->image_size
|
|
);
|
|
|
|
// load our base image in
|
|
$image_path = $this->image_path . "/qrv" . $this->version . ".png";
|
|
if (!file_exists($image_path)) {
|
|
throw new Image_QRCode_Exception("Base image not found");
|
|
}
|
|
$this->base_image = imagecreatefrompng($image_path);
|
|
if (!$this->base_image) {
|
|
throw new Image_QRCode_Exception("Couldn't load base image");
|
|
}
|
|
|
|
return $mib;
|
|
}
|
|
|
|
/**
|
|
* Adds the calculated matrix data to the base image.
|
|
*
|
|
* @param integer $max_modules_1side Max number of modules (single side)
|
|
* @param integer $mask_content Mask based on original data
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function addMatrixToImage($max_modules_1side, $mask_content)
|
|
{
|
|
// Create colours that we need to use
|
|
// NB if you want your barcode to be in a different colour,
|
|
// simply changing these colours will not work, as the base image
|
|
// is in black and white to start with.
|
|
$col[1] = imagecolorallocate($this->base_image, 0, 0, 0);
|
|
$col[0] = imagecolorallocate($this->base_image, 255, 255, 255);
|
|
|
|
$i = 4;
|
|
$mxe = 4 + $max_modules_1side;
|
|
$ii = 0;
|
|
while ($i < $mxe) {
|
|
$j = 4;
|
|
$jj = 0;
|
|
while ($j < $mxe) {
|
|
if ($this->matrix_content[$ii][$jj] & $mask_content) {
|
|
imagesetpixel($this->base_image, $i, $j, $col[1]);
|
|
}
|
|
$j++;
|
|
$jj++;
|
|
}
|
|
$i++;
|
|
$ii++;
|
|
}
|
|
}
|
|
}
|
|
?>
|