<?php

//  RMaxiCode for PHP
//  Copyright (C) Java4Less.com
//  All rights reserved
//
// Adquisition, use and distribution of this code is subject to restriction:
//  - You may modify the source code in order to adapt it to your needs.
//  - You may not remove this notice from the source code.
//  - This notice disclaim all warranties of all material.
//  - You may not copy and paste any code into external files.
//  - Use of this software on more than one server
//    requires the appropriate license.
//  - Redistribution of this source code (or a modified version)
//    is subject to the terms of the redistribution license.

require("Graphics.inc");
require("RBarcode2D.inc");
require("MaxiCode_globals.inc");
require("MaxiCode_core.inc");
require("ReedMC.inc");

class MaxiCode extends BarCode2D
{

////////// WORKING VARIABLES

var $core;

// offsets used when more than one symbol
var $XOffset;
var $YOffset;

////////// SOME PARAMETERS

var $processTilde;
var $numberOfSymbols;
var $SALayout;
var $browserDPI;
var $oneMoreXPixel;
var $oneMoreYPixel;

///////// CONSTRUCTOR

function MaxiCode() {

   parent::BarCode2D();
   $this->core = new MaxiCodecore( $this->g );
   $this->setQuiteZone(0);
   $this->processTilde = TRUE;
   $this->numberOfSymbols = 8;
   $this->SALayout = 'LR';
   $this->browserDPI = 96;
   $this->oneMoreXPixel = 0;
   $this->oneMoreYPixel = 0;
}

//////// SET MAXICODE SPECIFIC PARAMETERS

//  Set resolution of the printer in dpi.
//  Calculates a number of parameters based on it

function setResolution($r) {

   if ( ! is_numeric($r) ) return FALSE;

   // fix unreasonable values
   if ( $r <200 ) $r = 200;
   if ( $r > 1200 ) $r = 1200;
   
   if ( ! $this->core->calcParams($r) ) {
      // invalid resolution, use the next 100 multiple 
	  // (all 100 multiples -should- work)
      $r = 100 * round( ( $r  / 100 ) + 0.5 );
	  $this->core->calcParams($r);
   }
}

function setBrowserDPI ( $dpi ) {
   if ( is_numeric($dpi) )
      $this->browserDPI = $dpi;
}

// user-provided hexagon printing pattern
// see MaxiCode_globals.inc for examples

function setHexagonPattern( $p ) {
   $this->core->pattern = $p;
}

// MaxiCode mode. Values range from 2 to 6.

function setMode($m) {
   if ( ! is_numeric($m) || $m < 2 || $m > 6 ) {
      $this->doWarning( 'W_INVALID_PARAMETER', "Invalid mode $m");
	  return;
   }
   
   $this->core->mode = $m;
}

// set to TRUE to replace ~999 by ascii char 999

function setProcessTilde( $pt ) {
   $this->processTilde = $pt;
}

// maximum number of symbols in structured appended mode

function  setNumberOfSymbols( $n ) {
    if ( ! is_numeric( $n ) || $n < 1 || $n > 8 ) {
       $this->doWarning( 'W_INVALID_PARAMETER',"Invalid number of symbols $n");
       return;
	}
	//echo $n."<br>";
    $this->numberOfSymbols = $n;
}

function setSALayout( $l ) {

if ( $l == 'LR' || $l == 'RL' || $l == 'TB' || $l == 'BT')
   $this->SALayout = $l;

}

////// ""Structured Carrier Message" parameters (modes 2 and 3)

function setPostalCode( $pc ) {
   if ( strlen( $pc ) > 0 )  $this->core->postalCode = $pc;
}

function setCountry( $c ) {
   if ( is_numeric($c) )  $this->core->country = $c;
}

function setServiceClass( $sc ) {
   if ( is_numeric($sc) )  $this->core->serviceClass = $sc;
}

/////////////////////////////
//       P A I N T
/////////////////////////////

function paint( $data, $filename='' ) {

   // INITIALIZATIONS

   $this->core->data = $data;
  // echo $data."<br>";
   if ( $this->processTilde ) $this->applyTilde( $this->core->data );
   
   if ( ! $this->verifyParameters() ) return NULL;
         
   // for modes 2 or 3, encode the Structured Carrier Message
   // that will be placed on the MaxiCode primary message 

   $primary = $this->core->createPrimary();
   if ( $primary === NULL ) {
	  $this->doError( $this->core->errorCode, $this->core->errorMsg);
	  return NULL;
   }   
   
   // obtain maximum codeword capacity of the symbol depending on the mode
   
   switch ( $this->core->mode ) {
   case 5:
      $max_cw = 77; // 9 primary + 68 secondary -Enhanced error correction
	  break;
   case 4:
   case 6:
      $max_cw = 93; // 9 primary + 84 secondary - Standard error correction
      break;
   default:
      $max_cw = 84; // only secondary - Standard error correction
	  break;
   }

   // several symbols: deduct 2 codewords used for Structured Append
   if ( $this->numberOfSymbols > 1 ) $max_cw = $max_cw - 2;		 

   // ENCODE PROVIDED DATA 

   // Encodes user data for each symbol (if Structured Append is used).
   // Encoded strings are stored in $encode_arr.
   // If the maximum number of symbols is filled, remaining data is ignored.
   // Also obtains $ns, which is the number of symbols effetively used, 
   // which can be less than the maximum provided by $this->numberOfSymbols.
   
   $ns = 0; 
   //echo $this->numberOfSymbols."<br>";
   while( $ns < $this->numberOfSymbols ) {

      // end of data to encode, finish
      if ( $ns > 0 && strlen($this->core->data) == 0 ) break;
	  
	  $ns++;
	  	  
      // in case fits in one symbol, add two pad chars 
	  // to compensate initial $max_cw deduction
	  $plus2 = ( $ns == 1 && $this->numberOfSymbols > 1 ); 
	  //echo $this->core->data."<br>";
      if ( ( $encoded_arr[$ns] = $this->core->encode( $max_cw, $plus2 ) ) === NULL ){
	     $this->doError( $this->core->errorCode, $this->core->errorMsg );
		 return NULL;
	  }
   }
      /*foreach($encoded_arr as $key => $value){
      	echo $key."->".$value."<br>";
      	}*/
      	
      	//echo "----------------".$this->core->data."<br>";
   if ( strlen($this->core->data) > 0 )
      $this->doError( 'W_TRUNCATED', 'Data truncated: Last '.strlen($this->core->data).' chars lost' );
   
   // use default of 300 dpi if resolution was not set
   if ( $this->core->resolution === NULL )
      $this->setResolution(300);
	  
   // calculate symbol size,
   // image size (may contain several symbols),
   // and initial offsets (where the first symbol will be placed)
   $symbol_w = $this->core->prefW + $this->leftMargin * 2;
   $symbol_h = $this->core->prefH + $this->topMargin * 2;
  
   if ( $this->browserDPI > 0 )
      $this->adjustToBrowserDPI( $symbol_w, $symbol_h );
  
   $img_w = $symbol_w;
   $img_h = $symbol_h;
   $this->XOffset = $this->leftMargin;
   $this->YOffset = $this->topMargin;

   if ( $ns > 1 ) {
   
      if ( $this->SALayout == 'LR' || $this->SALayout == 'RL' )
         $img_w = $symbol_w * $ns;
	  else
         $img_h = $symbol_h * $ns;

      if ( $this->SALayout == 'RL' )
	     $this->XOffset += $img_w - $symbol_w; 
   
      if ( $this->SALayout == 'BT' )
	     $this->YOffset += $img_h - $symbol_h; 
   }
	  
   //*** image initializations
   
   $this->g->create( $img_w, $img_h );

   // Initialize colors & set background color (this fills image)
   $this->bgColorId   = $this->g->useColor( $this->bgColorDesc ); 
   $this->barColorId  = $this->g->useColor( $this->barColorDesc ); 
   $this->g->setColor( $this->bgColorId );

   //*** more initializations
   // hexagon pattern to be used for painting modules
   $this->core->loadPattern();

   ////// COMPLETE ENCODING & DRAW SYMBOL
		//echo "------".$ns."<br>";
   for ( $i = 1; $i <= $ns; $i++ ) {
   
      // structured append codewords   
      if ( $ns > 1 )
         $sa = PADAB. $this->core->parseBits(
		    $this->core->getBits( $i -1, 3 )
			.$this->core->getBits( $ns -1, 3 ) 
			);
      else
         $sa= '';

      // build complete encoded string & add error correction 
	  // note $primary already contains mode in modes 2 & 3
	  //    but it is empty for modes >3 
	  // $sa is empty if only one symbol

	  $encoded = '';
      if ( $this->core->mode > 3 ) $encoded = chr( $this->core->mode );
      $encoded .= $primary . $sa . $encoded_arr[$i];
     //echo $primary."<br>";
      $finalData = $this->core->addReedSolomon( $encoded );
			//echo "--".$finalData."<br>";
	  // Paint one symbol
	  
      $this->drawFinderPattern();
         
      // Set bar color as it will be used from now on
      $this->g->setColor( $this->barColorId );
      $this->core->paintLayout( $finalData, $this->XOffset , $this->YOffset );

      // update offsets if more than one symbol
      switch ( $this->SALayout ) {
	     case 'LR': $this->XOffset += $symbol_w; break;
	     case 'RL': $this->XOffset -= $symbol_w; break;
	     case 'TB': $this->YOffset += $symbol_h; break;
	     case 'BT': $this->YOffset -= $symbol_h; break;   
      }

      #if ( $i >0 ) $this->core->showCodes($finalData);  // debug
   }

 //echo "--".$finalData."<br>";
   // all done, generate image     
  $this->result = $this->g->outputImage( md5($data), $filename );
  
  return $this->result;
  
}


///////////////////////////////////////////////////////////
//       OTHER USER FUNCTIONS THAT CAN BE USED AFTER PAINT
///////////////////////////////////////////////////////////

function getImgTagWidth() {
   return round( $this->g->width * $this->browserDPI / $this->core->resolution );
}

function getImgTagHeight() {
   return round( $this->g->height * $this->browserDPI / $this->core->resolution );
}

function getImgTag( $src=NULL ) {
   
   if ( $src === NULL ) $src = $this->result;
   return "<img src=\"$src\" width=\"".$this->getImgTagWidth()
      ."\" height=\"".$this->getImgTagHeight().
	  "\" border=\"0\" alt=\"MaxiCode Symbol\" />\n";
}

///////////////////////////////////////////////////////////
//       AUXILIARY FUNCTIONS 
///////////////////////////////////////////////////////////

////////////////////////// VERIFY / ADJUST PARAMETER VALUES

function verifyParameters() {
  //echo "ùskodpqs<br>";
   // data to encode
   if ( $this->core->mode > 3 ) { 
      if ( strlen( $this->core->data ) == 0 ) {
         $this->doError( 'E_NO_DATA', 'No data to encode.' );
         
	     return FALSE;
      }	else {
      	//echo "-----";
	     return TRUE;
	  }
   }
//echo $this->core->postalCode." ùskodpqs<br>";
   // alias
   $pc = &$this->core->postalCode;
	  //echo $pc."<br>";
   // postal code not set, try extract primary messsage fields 
   // from provided data string to encode
   
   if ( ! $pc ) {
   		////echo "ici<br>";
      if ( ! $this->extractPrimaryData() ){
	     //echo "ixi<br>";
	     return FALSE;
	    }
		}//else echo "ixixii<br>";
   // if alphanumeric postal code, switch to mode 3
   if ( $this->core->mode == 2 && ! is_numeric($pc) ) {
      $this->setMode(3);
      $this->doError( 'N_MODE_CHANGED', "Non numeric postal code $pc, switched to mode 3.");		 
   }

   // Verify / adjust Postal Code 
//echo "cp<br>";
   if ( $this->core->mode == 2 ) {
      if ( $pc <= 0 || $pc > pow(2,30) ) {
         $this->doError( 'E_INVALID_DATA', "Invalid postal code for mode 2: $pc");
         //echo "ùskodpqs<br>";
	     return FALSE;
	  }
   } else {
      // truncate or pad to 6 chars
      $pc = str_pad( substr( $pc, 0, 6 ), 6 );
   }
	  
   // verify Country 
   $c = &$this->core->country;
   if ( ! is_numeric($c) || $c <= 0 || $c > pow(2,10) ) {
      $this->doError( 'E_INVALID_DATA', "Invalid country code: $c");
      return FALSE;
   }	  
  
   // verify Service Class
   
   $sc = &$this->core->serviceClass;
   if ( ! is_numeric($sc) || $sc <= 0 || $sc > pow(2,10) ) {
      $this->doError( 'E_INVALID_DATA', "Invalid service class: $sc");
	  return FALSE;
   }	  
   //echo "iiiii";
   // all verifications passed
   return TRUE; 
}

////////// EXTRACT PRIMARY DATA

// Obtains Structured Carrier Message fields (postal code, country and service)
// when included in data starting with the sequence "[)<RS>01<GS>" 

function extractPrimaryData() {
   //echo "extractPrimaryData"."<br>";
   $data = &$this->core->data;
   //echo $data."<br>";
   //echo substr( $data, 0 , 7 )."<br>";
   //echo '[)>'.RS.'01'.GS."<br>";
   if ( substr( $data, 0 , 7 ) == '[)>'.RS.'01'.GS ) {
      $start = 9;
      $isec = substr( $data, 0, 9 );    
	  $data = substr( $data, 9 );
   } else {
      $start = 0;
      $isec = '';      
   }

   $fields = explode( GS, $data, 4 );

   if ( count( $fields ) < 3 ) {
      $this->doError( 'E_INVALID_DATA', 'Some mandatory field is missing' );
	  return FALSE;
   }

   $this->core->postalCode = $fields[0];
   $this->core->country = $fields[1];
   $this->core->serviceClass = $fields[2];

   // secondary message
   if ( count( $fields ) == 3 )
      $data = '';   
   else
      $data = $isec . $fields[3];

   return TRUE;
}

////////// APPLY TILDE

function applyTilde( &$data ) {

   $len = strlen($data);
   $result = '';
   $i = 0;

   while ( $i < $len ) {
        
      // search next tilde
      $j = strpos( $data, '~' , $i );
	  
	  // no more tildes or no room for three digits, finish
	  if ( $j === FALSE || $j + 3 >= $len  ) {
	     $result .= substr( $data, $i );
		 break;
	  }

      // add chars until tilde		 
      if ( $j > $i ) {
	     $result .= substr( $data, $i, $j - $i );
         $i = $j;
	  }

      // check if a valid 3 digits number follows
      $ascii = substr( $data, $j+1, 3 );
	  if ( ! is_numeric( $ascii ) || $ascii > 255 ) {
	     $result .= '~';
		 $i++;
		 continue;
	  }

      // OK, found.
	  
	  $result .= chr($ascii);
	  $i += 4;	       	 
   }

   $data = $result;
}


////////// DRAW FINDER PATTERN

function drawFinderPattern() {

   $this->g->setColor( $this->barColorId );
   $this->finderCircle( 3.87 );
   $this->g->setColor( $this->bgColorId );
   $this->finderCircle( 3.20 );
   $this->g->setColor( $this->barColorId );   
   $this->finderCircle( 2.53 );
   $this->g->setColor( $this->bgColorId );
   $this->finderCircle( 1.86 );
   $this->g->setColor( $this->barColorId );   
   $this->finderCircle( 1.18 );
   $this->g->setColor( $this->bgColorId );
   $this->finderCircle( 0.51 );
}

function finderCircle( $r ) {

   $this->g->circle( 
      $this->core->centerX + $this->XOffset,
      $this->core->centerY + $this->YOffset,
      round( $r * $this->core->dpm )
   );
}

//////////// ADJUSTMENTS TO BROWSER PRINTING DENSITY (DOTS-PER-INCH)

function  adjustToBrowserDPI( &$symbol_w, &$symbol_h ) {

   // obtain additional pixels to add
   // second parameter : maximum MaxiCode size in mm plus a 5%
   // (we can do it greater than maximum as we only increase the quite zone)
   $xadd = $this->adjustSize( $symbol_w, 29.79 * 1.05 ); 
   $yadd = $this->adjustSize( $symbol_h, 28.49 * 1.05 ); // max height in mm   

   // add adjusment pixels to symbol size
   $symbol_w += $xadd;
   $symbol_h += $yadd;
      
   // increase margins to keep symbol centered
   $this->leftMargin += (int) ( $xadd  / 2 );
   $this->topMargin += (int) ( $yadd  / 2 );
}

function adjustSize( $actual, $max_mm ) {

   // minumm & maximium values for HTML IMG tag
   $tag_min = (int) round( $actual * $this->browserDPI / $this->core->resolution );
   $tag_max = (int) ( ($max_mm/25.5) * $this->browserDPI );    
   
   $difer = 10;
   $best = $actual;
   
   for ( $i = $tag_min; $i <= $tag_max; $i++ ) {  
      $new = (int) 0;
      $new = $i * $this->core->resolution / $this->browserDPI;
	  #echo "<br> NEW $i - $new ";
      if ( is_int( $new ) )
         // found exact match
         return $new - $actual;
	  else {
	     $new_difer = $new - (int) $new;
		 $new = (int) $new;
		 if ( $new_difer > 0.5 ) {
		    $new++;
			$new_difer = 1 - $new_difer;
		 }
		 #echo " difer $new_difer";

		 if ( $new_difer < $difer ) {
		    $difer = $new_difer;
			$best = $new;
			#echo " -- best $best";
		}  

	  }
	  
   }
   // none exact found, use smaller
   return $best - $actual;
}

} // of class MaxiCode

?>
