<?
class orcad21
{
	const HDR_SIZE = 48;

	public $Error;
	public $LastErrorStr;

	public $ParseIndex;

	public $FileName;
	public $FileSize;
	public $BUF = array();
	public $HDR = array();
	public $COMP = array();
	public $TXT = array();
	public $BRDPOI = array();
	public $NETPOI = array();

	public $VIA_STYLE = array();
	public $PAD_STYLE = array();
	public $TXT_STYLE = array();

	public $PATTERN_STYLE = array();


	public $MinPosX;                                   
	public $MinPosY;                                   
	public $MaxPosX;                                   
	public $MaxPosY;                                   

	// constructor
	function __construct($fn = "")
	{
		$this->FileName = $fn;
		$this->Load($this->FileName);

		$this->MinPosX = 1000000;
		$this->MinPosY = 1000000;
		$this->MaxPosX = 0;
		$this->MaxPosY = 0;
	}

	// private
	private function hex($int) 
	{
		$h = dechex($int);
		if (strlen($h)%2 != 0) 
		{
			$h = str_pad($h, strlen($h) + 1, '0', STR_PAD_LEFT);
		}
		return strtoupper($h)."h";
	}

	private function GetStr($strl, $descr = "")
	{
		$result['ofs'] = $this->ParseIndex;
		for ($i=0; $i<$strl; $i++)
		{
			if ($i > 0) $result['buf'] .= " ";
			$result['buf'] .= $this->hex(ord($this->BUF[$this->ParseIndex + $i]));
			if ((ord($this->BUF[$this->ParseIndex + $i]) > 31) and (ord($this->BUF[$this->ParseIndex + $i]) < 128)) 
			{
				$result['v'] .= $this->BUF[$this->ParseIndex + $i];
			}
		}
		$result['descr'] = $descr;
		$this->ParseIndex += $strl;
		return $result;
	}

	private function GetByte($descr = "")
	{
		$result['ofs'] = $this->ParseIndex;
		$result['buf'] = $this->hex(ord($this->BUF[$this->ParseIndex]));
		$result['v'] = ord($this->BUF[$this->ParseIndex]);
		$result['descr'] = $descr;
		$this->ParseIndex++;
		return $result;
	}

	private function GetWord($descr = "")
	{
		$result['ofs'] = $this->ParseIndex;
		$result['buf'] = $this->hex(ord($this->BUF[$this->ParseIndex])) . " " . $this->hex(ord($this->BUF[$this->ParseIndex + 1]));
		$result['v'] = ord($this->BUF[$this->ParseIndex]) + 256 * ord($this->BUF[$this->ParseIndex + 1]);
		$result['descr'] = $descr;
		$this->ParseIndex += 2;
		return $result;
	}

	private function ParseHeader()
	{
		$this->Error = 0;
		$this->LastErrorStr = "";

		$this->ParseIndex = 0;
		$this->HDR['file_name'] = $this->GetStr(8);
		$this->HDR['file_name']['v'] = trim($this->HDR['file_name']['v']);
		$this->HDR['unk1'] = $this->GetWord();
		$this->HDR['comp_count'] = $this->GetWord();
		$this->HDR['unk3'] = $this->GetWord();
		$this->HDR['unk4'] = $this->GetWord();
		$this->HDR['unk5'] = $this->GetWord();
		$this->HDR['unk6'] = $this->GetWord();
		$this->HDR['unk7'] = $this->GetWord();
		$this->HDR['unk8'] = $this->GetWord();
		$this->HDR['unk9'] = $this->GetWord();
		$this->HDR['unk10'] = $this->GetWord();
		$this->HDR['unk11'] = $this->GetWord();
		$this->HDR['texts_ofs'] = $this->GetWord();
		$this->HDR['texts_ofs_real']['v'] = $this->HDR['texts_ofs']['v'] * 2 + self::HDR_SIZE;
		$this->HDR['texts_size'] = $this->GetWord();
		$this->HDR['brd_points_count'] = $this->GetWord();
		$this->HDR['net_points_count'] = $this->GetWord();
		$this->HDR['unk16'] = $this->GetWord();
		$this->HDR['unk17'] = $this->GetWord();
		$this->HDR['unk18'] = $this->GetWord();
		$this->HDR['file_ext'] = $this->GetStr(4);
		$this->HDR['file_ext']['v'] = trim($this->HDR['file_ext']['v']);

		// check range
		if ($this->ParseIndex > $this->FileSize)
		{
			$this->Error = 1;
			$this->LastErrorStr = "ParseIndex is out of FileSize during ParseHeader()";
			return false;
		}

		return true;
	}

	private function ParseComponent($i)
	{
		$this->COMP[$i]['ofs'] = $this->ParseIndex;

		$this->COMP[$i]['timestamp'] = $this->GetStr(8);
		$this->COMP[$i]['unk1'] = $this->GetByte();
		$this->COMP[$i]['rotate'] = $this->GetByte("0-0, 1-90, 2-180, 3-270");
		$this->COMP[$i]['layer'] = $this->GetByte("0-bot, 1-top");
		$this->COMP[$i]['unk3'] = $this->GetByte();
		$this->COMP[$i]['unk4'] = $this->GetWord();
		$this->COMP[$i]['unk5'] = $this->GetWord();
		$this->COMP[$i]['unk6'] = $this->GetWord();
		$this->COMP[$i]['unk7'] = $this->GetWord();
		$this->COMP[$i]['pos_y'] = $this->GetWord();
		$this->COMP[$i]['pos_x'] = $this->GetWord();
		$this->COMP[$i]['unk10'] = $this->GetWord();
		$this->COMP[$i]['unk11'] = $this->GetWord();
		$this->COMP[$i]['lines_ofs'] = $this->GetWord();
		$this->COMP[$i]['names_ofs'] = $this->GetWord();
		$this->COMP[$i]['pads_ofs'] = $this->GetWord();
		$this->COMP[$i]['next_ofs1'] = $this->GetWord();
		$this->COMP[$i]['next_ofs2'] = $this->GetWord();
		$this->COMP[$i]['pattern'] = $this->GetStr(8);
		$this->COMP[$i]['pattern']['v'] = trim($this->COMP[$i]['pattern']['v']);
		$this->COMP[$i]['next_ofs3'] = $this->GetWord();

		$this->COMP[$i]['lines_count']['v'] = ($this->COMP[$i]['names_ofs']['v'] - $this->COMP[$i]['lines_ofs']['v']) / 6;
		$this->COMP[$i]['names_count']['v'] = ($this->COMP[$i]['pads_ofs']['v'] - $this->COMP[$i]['names_ofs']['v']) / 24;
		$this->COMP[$i]['pads_count']['v'] = ($this->COMP[$i]['next_ofs1']['v'] - $this->COMP[$i]['pads_ofs']['v']) / 24;

		// lines
		for($j=1; $j<=$this->COMP[$i]['lines_count']['v']; $j++)
		{
			$this->COMP[$i]['line'.$j.'_flags'] = $this->GetWord();
			$this->COMP[$i]['line'.$j.'_pos_y'] = $this->GetWord();
			$this->COMP[$i]['line'.$j.'_pos_x'] = $this->GetWord();
		}

		// names
		for($j=1; $j<=$this->COMP[$i]['names_count']['v']; $j++)
		{
			$this->COMP[$i]['name'.$j.'_unk1'] = $this->GetWord();
			$this->COMP[$i]['name'.$j.'_unk2'] = $this->GetWord();
			$this->COMP[$i]['name'.$j.'_str'] = $this->GetStr(8);
			$this->COMP[$i]['name'.$j.'_str']['v'] = trim($this->COMP[$i]['name'.$j.'_str']['v']);
			$this->COMP[$i]['name'.$j.'_unk4'] = $this->GetWord();
			$this->COMP[$i]['name'.$j.'_unk5'] = $this->GetWord();
			$this->COMP[$i]['name'.$j.'_unk6'] = $this->GetWord();
			$this->COMP[$i]['name'.$j.'_unk7'] = $this->GetWord();
			$this->COMP[$i]['name'.$j.'_pos_y'] = $this->GetWord();
			$this->COMP[$i]['name'.$j.'_pos_x'] = $this->GetWord();
		}

		// pads
		for($j=1; $j<=$this->COMP[$i]['pads_count']['v']; $j++)
		{
			$this->COMP[$i]['pad'.$j.'_ofs_real']['v'] = $this->ParseIndex;
			$this->COMP[$i]['pad'.$j.'_ofs']['v'] = round($this->ParseIndex / 2) - 24;
			$this->COMP[$i]['pad'.$j.'_des'] = $this->GetStr(4);
			$this->COMP[$i]['pad'.$j.'_des']['v'] = trim($this->COMP[$i]['pad'.$j.'_des']['v']);
			$this->COMP[$i]['pad'.$j.'_net'] = $this->GetStr(8);
			$this->COMP[$i]['pad'.$j.'_net']['v'] = trim($this->COMP[$i]['pad'.$j.'_net']['v']);
			$this->COMP[$i]['pad'.$j.'_shape'] = $this->GetByte("1-circ, 2-rect");
			$this->COMP[$i]['pad'.$j.'_width'] = $this->GetWord();
			$this->COMP[$i]['pad'.$j.'_height'] = $this->GetWord();
			$this->COMP[$i]['pad'.$j.'_rotate'] = $this->GetByte("0-0, 1-90, 2-180, 3-270");
			$this->COMP[$i]['pad'.$j.'_flags'] = $this->GetByte("1-smd, 3-plated");
			$this->COMP[$i]['pad'.$j.'_hole_dia'] = $this->GetByte();
			$this->COMP[$i]['pad'.$j.'_pos_y'] = $this->GetWord();
			$this->COMP[$i]['pad'.$j.'_pos_x'] = $this->GetWord();
		}
	}

	private function ParseComponents()
	{
		$this->Error = 0;
		$this->LastErrorStr = "";

		// 'comp_count' = 'comp_count_real' + 1, 
		// so needs check 'ParseIndex' does not over 'texts_ofs_real'
		for($i=1; $i<=$this->HDR['comp_count']['v']; $i++)
		{
			$this->ParseComponent($i);

			// check components size
			if($this->ParseIndex >= $this->HDR['texts_ofs_real']['v'])
			{
				$this->HDR['comp_count_real']['v'] = $i;
				return true;
			}

			// check range
			if ($this->ParseIndex > $this->FileSize)
			{
				$this->Error = 1;
				$this->LastErrorStr = "ParseIndex is out of FileSize during ParseComponents(), i=" . $i;
				return false;
			}
		}

		return false;
	}

	private function ParseTexts()
	{
		$this->Error = 0;
		$this->LastErrorStr = "";

		$i = 1;
		while (1)
		{                                                                                    
			$this->TXT[$i]['ofs'] = $this->ParseIndex;

			$this->TXT[$i]['unk1'] = $this->GetWord();
			$this->TXT[$i]['unk2'] = $this->GetWord();
			$this->TXT[$i]['unk3'] = $this->GetWord();
			$this->TXT[$i]['unk4'] = $this->GetWord();
			$this->TXT[$i]['unk5'] = $this->GetWord();
			$this->TXT[$i]['unk6'] = $this->GetWord();
			$this->TXT[$i]['len'] = $this->GetByte();
			$this->TXT[$i]['size_h'] = $this->GetWord();
			$this->TXT[$i]['size_v'] = $this->GetWord();
			$this->TXT[$i]['rotate'] = $this->GetByte("0-0, 1-90, 2-180, 3-270");
			$this->TXT[$i]['layer'] = $this->GetByte("0-bot, 1-top");
			$this->TXT[$i]['flags'] = $this->GetByte("(HL), H-mirrored(0-yes)");
			$this->TXT[$i]['pos_y'] = $this->GetWord();
			$this->TXT[$i]['pos_x'] = $this->GetWord();
			$this->TXT[$i]['str'] = $this->GetStr($this->TXT[$i]['len']['v']);

			// check texts size
			if ($this->ParseIndex >= ($this->HDR['texts_ofs_real']['v'] + $this->HDR['texts_size']['v']))
				break;

			// check range
			if ($this->ParseIndex > $this->FileSize)
			{
				$this->Error = 1;
				$this->LastErrorStr = "ParseIndex is out of FileSize during ParseTexts(), i=" . $i;
				return false;
			}

			$i++;
		}

		$this->HDR['texts_count']['v'] = $i;
		return true;
	}

	private function ParseBoardPoints()
	{
		$this->Error = 0;
		$this->LastErrorStr = "";

		for($i=1; $i<=$this->HDR['brd_points_count']['v']; $i++)
		{

			$this->BRDPOI[$i]['ofs'] = $this->ParseIndex;

			$this->BRDPOI[$i]['flags'] = $this->GetByte();
			$this->BRDPOI[$i]['unk2'] = $this->GetByte();
			$this->BRDPOI[$i]['pos_y'] = $this->GetWord();
			$this->BRDPOI[$i]['pos_x'] = $this->GetWord();
			$this->BRDPOI[$i]['unk5'] = $this->GetByte();
			$this->BRDPOI[$i]['unk6'] = $this->GetByte();

			if ($this->BRDPOI[$i]['pos_x']['v'] < $this->MinPosX) $this->MinPosX = $this->BRDPOI[$i]['pos_x']['v'];
			if ($this->BRDPOI[$i]['pos_y']['v'] < $this->MinPosY) $this->MinPosY = $this->BRDPOI[$i]['pos_y']['v'];

			if ($this->BRDPOI[$i]['pos_x']['v'] > $this->MaxPosX) $this->MaxPosX = $this->BRDPOI[$i]['pos_x']['v'];
			if ($this->BRDPOI[$i]['pos_y']['v'] > $this->MaxPosY) $this->MaxPosY = $this->BRDPOI[$i]['pos_y']['v'];

			// check range
			if ($this->ParseIndex > $this->FileSize)
			{
				$this->Error = 1;
				$this->LastErrorStr = "ParseIndex is out of FileSize during ParseBoardPoints(), i=" . $i;
				return false;
			}
		}

		return true;
	}

	private function ParseNetPoints()
	{
		$this->Error = 0;
		$this->LastErrorStr = "";

		for($i=1; $i<=$this->HDR['net_points_count']['v']; $i++)
		{

			$this->NETPOI[$i]['ofs'] = $this->ParseIndex;

			$this->NETPOI[$i]['flags'] = $this->GetByte("(1x)h-layer, (x1)h-head");
			$this->NETPOI[$i]['size'] = $this->GetByte();
			$this->NETPOI[$i]['pos_y'] = $this->GetWord();
			$this->NETPOI[$i]['pos_x'] = $this->GetWord();
			$this->NETPOI[$i]['net_id'] = $this->GetWord();

			// check range
			if ($this->ParseIndex > $this->FileSize)
			{
				$this->Error = 1;
				$this->LastErrorStr = "ParseIndex is out of FileSize during ParseNetPoints(), i=" . $i;
				return false;
			}
		}

		return true;
	}

	// public
	public function Load()
	{
		$this->ParseIndex = 0;
		$this->BUF = file_get_contents($this->FileName);
		$this->FileSize = filesize($this->FileName);
		if (!$this->FileSize)
		{
			$this->FileSize = 0;
		}
	}

	public function Parse()
	{
		$this->ParseHeader();
		$this->ParseComponents();
		$this->ParseTexts();
		$this->ParseBoardPoints();
		$this->ParseNetPoints();

		if ($this->FileSize == $this->ParseIndex)
			return true;

		return false;
	}

	public function ChangePos($dx, $dy, $reverse_x, $reverse_y, $swap = 0)
	{
		// component
		for($i=1; $i<=sizeof($this->COMP); $i++)
		{
			if (!$this->COMP[$i]['ofs']) continue;

			$this->COMP[$i]['pos_x']['v'] += $dx;
			$this->COMP[$i]['pos_y']['v'] += $dy;
			if ($reverse_x) $this->COMP[$i]['pos_x']['v'] = $reverse_x - $this->COMP[$i]['pos_x']['v'];
			if ($reverse_y) $this->COMP[$i]['pos_y']['v'] = $reverse_y - $this->COMP[$i]['pos_y']['v'];
			if ($swap)
			{
				$tmp = $this->COMP[$i]['pos_x']['v'];
				$this->COMP[$i]['pos_x']['v'] = $this->COMP[$i]['pos_y']['v'];
				$this->COMP[$i]['pos_y']['v'] = $tmp;
			}

			// lines
			for($j=1; $j<=$this->COMP[$i]['lines_count']['v']; $j++)
			{
				$this->COMP[$i]['line'.$j.'_pos_y']['v'] += $dy;
				$this->COMP[$i]['line'.$j.'_pos_x']['v'] += $dx;
				if ($reverse_x) $this->COMP[$i]['line'.$j.'_pos_x']['v'] = $reverse_x - $this->COMP[$i]['line'.$j.'_pos_x']['v'];
				if ($reverse_y) $this->COMP[$i]['line'.$j.'_pos_y']['v'] = $reverse_y - $this->COMP[$i]['line'.$j.'_pos_y']['v'];
				if ($swap)
				{
					$tmp = $this->COMP[$i]['line'.$j.'_pos_x']['v'];
					$this->COMP[$i]['line'.$j.'_pos_x']['v'] = $this->COMP[$i]['line'.$j.'_pos_y']['v'];
					$this->COMP[$i]['line'.$j.'_pos_y']['v'] = $tmp;
				}
			}

			// names
			for($j=1; $j<=$this->COMP[$i]['names_count']['v']; $j++)
			{
				$this->COMP[$i]['name'.$j.'_pos_y']['v'] += $dy;
				$this->COMP[$i]['name'.$j.'_pos_x']['v'] += $dx;
				if ($reverse_x) $this->COMP[$i]['name'.$j.'_pos_x']['v'] = $reverse_x - $this->COMP[$i]['name'.$j.'_pos_x']['v'];
				if ($reverse_y) $this->COMP[$i]['name'.$j.'_pos_y']['v'] = $reverse_y - $this->COMP[$i]['name'.$j.'_pos_y']['v'];
				if ($swap)
				{
					$tmp = $this->COMP[$i]['name'.$j.'_pos_x']['v'];
					$this->COMP[$i]['name'.$j.'_pos_x']['v'] = $this->COMP[$i]['name'.$j.'_pos_y']['v'];
					$this->COMP[$i]['name'.$j.'_pos_y']['v'] = $tmp;
				}
			}

			// pads
			for($j=1; $j<=$this->COMP[$i]['pads_count']['v']; $j++)
			{
				$this->COMP[$i]['pad'.$j.'_pos_y']['v'] += $dy;
				$this->COMP[$i]['pad'.$j.'_pos_x']['v'] += $dx;
				if ($reverse_x) $this->COMP[$i]['pad'.$j.'_pos_x']['v'] = $reverse_x - $this->COMP[$i]['pad'.$j.'_pos_x']['v'];
				if ($reverse_y) $this->COMP[$i]['pad'.$j.'_pos_y']['v'] = $reverse_y - $this->COMP[$i]['pad'.$j.'_pos_y']['v'];
				if ($swap)
				{
					$tmp = $this->COMP[$i]['pad'.$j.'_pos_x']['v'];
					$this->COMP[$i]['pad'.$j.'_pos_x']['v'] = $this->COMP[$i]['pad'.$j.'_pos_y']['v'];
					$this->COMP[$i]['pad'.$j.'_pos_y']['v'] = $tmp;

					$tmp = $this->COMP[$i]['pad'.$j.'_width']['v'];
					$this->COMP[$i]['pad'.$j.'_width']['v'] = $this->COMP[$i]['pad'.$j.'_height']['v'];
					$this->COMP[$i]['pad'.$j.'_height']['v'] = $tmp;
				}
			}
		}

		// text
		for($i=1; $i<=sizeof($this->TXT); $i++)
		{
			$this->TXT[$i]['pos_y']['v'] += $dy;
			$this->TXT[$i]['pos_x']['v'] += $dx;
			if ($reverse_x) $this->TXT[$i]['pos_x']['v'] = $reverse_x - $this->TXT[$i]['pos_x']['v'];
			if ($reverse_y) $this->TXT[$i]['pos_y']['v'] = $reverse_y - $this->TXT[$i]['pos_y']['v'];
			if ($swap)
			{
				$tmp = $this->TXT[$i]['pos_x']['v'];
				$this->TXT[$i]['pos_x']['v'] = $this->TXT[$i]['pos_y']['v'];
				$this->TXT[$i]['pos_y']['v'] = $tmp;
			}
		}

		// board points
		for($i=1; $i<=$this->HDR['brd_points_count']['v']; $i++)
		{
			$this->BRDPOI[$i]['pos_y']['v'] += $dy;
			$this->BRDPOI[$i]['pos_x']['v'] += $dx;
			if ($reverse_x) $this->BRDPOI[$i]['pos_x']['v'] = $reverse_x - $this->BRDPOI[$i]['pos_x']['v'];
			if ($reverse_y) $this->BRDPOI[$i]['pos_y']['v'] = $reverse_y - $this->BRDPOI[$i]['pos_y']['v'];
			if ($swap)
			{
				$tmp = $this->BRDPOI[$i]['pos_y']['v'];
				$this->BRDPOI[$i]['pos_y']['v'] = $this->BRDPOI[$i]['pos_x']['v'];
				$this->BRDPOI[$i]['pos_x']['v'] = $tmp;
			}
		}

		// net pointes
		for($i=1; $i<=$this->HDR['net_points_count']['v']; $i++)
		{
			$this->NETPOI[$i]['pos_y']['v'] += $dy;
			$this->NETPOI[$i]['pos_x']['v'] += $dx;
			if ($reverse_x) $this->NETPOI[$i]['pos_x']['v'] = $reverse_x - $this->NETPOI[$i]['pos_x']['v'];
			if ($reverse_y) $this->NETPOI[$i]['pos_y']['v'] = $reverse_y - $this->NETPOI[$i]['pos_y']['v'];
			if ($swap)
			{
				$tmp = $this->NETPOI[$i]['pos_x']['v'];
				$this->NETPOI[$i]['pos_x']['v'] = $this->NETPOI[$i]['pos_y']['v'];
				$this->NETPOI[$i]['pos_y']['v'] = $tmp;
			}
		}

	}


}
