<?php


/**
 * Adapted from the work of Paul Haahr, http://www.webcom.com/~haahr/
 *   http://www.stillhq.com/cgi-bin/cvsweb/ascii85/
 * 
*/
/** 
 * @package ASCII85 
 * @example  /ascii85example.php  Example usage of this class.
 * @category   Numbers
 * @author Sam Shull <samshull@samshull.com>
 * @copyright Copyright (c) 2007, Sam Shull
 * @license http://www.samshull.com/bsdlicense.txt BSD License
 * @link       http://samshull.com/ascii85example.php
 * @version    0.9
 * @access     public
*/
class ASCII85{
/**
 * Line width for splitting
 *
 * @var integer
 * @access protected
 */
 var $width = 72;
/**
 * Position within the line
 *
 * @var integer
 * @access protected
 */
 var $pos = 0;
/**
 * Unsigned long being manipulated
 *
 * @var string
 * @access protected
 */
 var $tuple = "0";
/**
 * Number of bytes being manipulated
 *
 * @var integer
 * @access protected
 */
 var $count = 0;
/**
 * Output
 *
 * @var string
 * @access protected
 */
 var $out = "";
/**
 * Power of 85 multiplier
 *
 * @var array
 * @access protected
 */
 var $pow85;
/**
 * Error
 *
 * @var string
 * @access public
 */
 var $error;
/**
 * For storing unpacked bytes
 *
 * @var array
 * @access protected
 */
 var $array = array();
/**
 * Position within byte array
 *
 * @var integer
 * @access protected
 */
 var $i = 1;

/**
 * Method: encode  
 *   Primary encoding method, one argument, the string that is to be encoded
 * @param string $string
 * @return string
**/
function encode($string){
   $this->error = "";
   $this->out = "";
	$this->pos = 2;
	
	  $array = unpack("C*",$string);
	  //print_r($array);
	  for($i=1;$i<=count($array);$i++){
		$this->put85($array[$i]);
	  }
	  
   if ($this->count > 0)
		$this->encode85(false);
	if ($this->pos + 2 > $this->width)
		$this->out.="\n";
	$this->out.="~>\n";
	  if($this->error){
	    return $this->error;
	  }else{
	    return $this->out;
	  }
}

/**
 * Method: encode85  - if PHP5 mark as private or protected
 *     Method used to convert an unsigned long to ASCII characters
 *      One parameter bool increase the count by one when adding 
 *        encoded characters to output string
 * @param bool $tru default:true
**/
function encode85($tru=true) {
	$s = array();
	$i = 5;
	 while (--$i >= 0){
		$s[$i] = (int)bcmod($this->tuple,"85");
		$this->tuple = bcdiv($this->tuple,"85");
	}
	//print_r($s);
	$f = $tru ? 1 : 0;
	 for($i=0;$i<=$this->count+$f;$i++){
		$this->out .= chr(($s[$i] + ord('!')));
		if ($this->pos++ >= $this->width) {
			$this->pos = 0;
			$this->out.="\n";
		}
	}
}
/**
 * Method: put85  - if PHP5 mark as private or protected
 *    Method is passed each char of the string to be encoded and adds it
 *     to an unsigned long for conversion by encode85
 * @param decimal $c
**/
function put85($c) {
	switch ($this->count) {
	case 0:	$this->tuple = bcadd($this->lshift($c,24),$this->tuple); 
			$this->count++; 
			break;
	case 1: $this->tuple = bcadd($this->tuple,((string)($c << 16))); 
			 $this->count++; 
			 break;
	case 2:	$this->tuple = bcadd($this->tuple,((string)($c << 8))); 
			$this->count++; 
			break;
	case 3:
		$this->tuple = bcadd($this->tuple,((string)$c));
		if ($this->tuple == 0) {
			$this->out.='z';
			if ($this->pos++ >= $this->width) {
				$this->pos = 0;
				$this->out.="\n";
			}
		} else {
			$this->encode85();
		}
		$this->tuple = "0";
		$this->count = 0;
		break;
	}
}
/**
 * Method: decode
 *   Primary method used to decode an encoded string, one parameter an encoded string
 *     Breaks apart string for encoding and returns
 * @param string $string
 * @return string
**/
  function decode($string){
      $this->error = "";
	  $this->out = "";
	  $this->count = 0;
      $this->pow85 = array((85*85*85*85), (85*85*85), (85*85), 85, 1);
	  $string=preg_replace("/^<~/isx","",$string);
	  $this->array = str_split($string);
	  while($this->i < count($this->array)){
		$this->decode85(current($this->array));
		next($this->array);
		$this->i++;
	  }
	  if($this->error){
	    return $this->error;
	  }else{
	    return $this->out;
	  }
  }
/**
 *  Method: wput - if PHP5 mark as private or protected
 *     Used to pack the output codes, one parameter number of bytes to output
 * @param int $bytes
**/
  function wput($bytes) {
	switch ($bytes) {
	case 4:
		$this->out.=pack("C",$this->rshift($this->tuple,24));
		$this->out.=pack("C",$this->rshift($this->tuple,16));
		$this->out.=pack("C",$this->rshift($this->tuple,8));
		$this->out.=pack("C",((float)$this->tuple));
		break;
	case 3:
		$this->out.=pack("C",$this->rshift($this->tuple,24));
		$this->out.=pack("C",$this->rshift($this->tuple,16));
		$this->out.=pack("C",$this->rshift($this->tuple,8));
		break;
	case 2:
		$this->out.=pack("C",$this->rshift($this->tuple,24));
		$this->out.=pack("C",$this->rshift($this->tuple,16));
		break;
	case 1:
		$this->out.=pack("C",$this->rshift($this->tuple,24));
		break;
	}
	//$this->tuple = "0";
  }
/**
 * Method: decode85 - if PHP5 mark as private or protected
 *   Used to decode the chars and add them up in an unsigned long
 *     to be encoded, one paramater char to be added
 * @param char $c
**/
  function decode85($c) {
	switch ($c) {
		case 'z':
			if ($this->count != 0) {
				$this->error.="\n: z inside ascii85 5-tuple";
				return;
			}
			$this->out.=pack("C",0x00);
			$this->out.=pack("C",0x00);
			$this->out.=pack("C",0x00);
			$this->out.=pack("C",0x00);
			break;
		case '~':
			$c = next($this->array);
			if ($c == '>') {
				if ($this->count > 0) {
					$this->count--;
					$this->tuple = bcadd($this->tuple,$this->pow85[$this->count]);
					$this->wput($this->count);
				}
				return;
			}
			$this->error.="\n: ~ without > in ascii85 section";
			return;
		case "\n": case "\r": case "\t": case " ":
		case "\0": case "\f": case "\b": case 0177:
			break;
		default:
		    //echo (ord($c)-ord('!'))."\n";
			if (ord($c) < ord('!') || ord($c) > ord('u')) {
				$this->error.="\nBad character in ascii85 region: ".current($this->array)." ".$this->i;
				//return;
			}
			$this->tuple = bcadd($this->tuple,bcmul((ord($c)-ord('!')),$this->pow85[$this->count]));
			$this->count++;
			if ($this->count == 5) {
				$this->wput(4);
				$this->count = 0;
				$this->tuple = "0";
			}
			break;
		}
  }
/**
 * Method: lshift - if PHP5 mark as private or protected
 *   Used to allow class to deal with unsigned longs, bitwise left shift
 *    Two parameters, number to be shifted, and how much to shift
 * @param int|string $n
 * @param int $b
 * @return string
**/
  function lshift($n,$b){
   for($t=0;$t<$b;$t++){
      $n = bcmul($n,"2");
   }
   return ((string)$n);
  }
/**
 * Method: rshift - if PHP5 mark as private or protected
 *   Used to allow class to deal with unsigned longs, bitwise right shift
 *    Two parameters, number to be shifted, and how much to shift
 * @param int $n
 * @param int $b
 * @return int
 */
  function rshift($n,$b){
   for($t=0;$t<$b;$t++){
      $n = bcdiv($n,"2");
   }
   return ((int)$n);
  }
}

?>
