<?php
// Copyright Howard Jones, 2005 howie@thingy.com
// http://wotsit.thingy.com/haj/cacti/
// Released under the GNU Public License

// A simple port of the guts of Apache's mod_imap
// - if you have an image control in a form, it's not really defined what happens to USEMAP
//   attributes. They are allowed in HTML 4.0 and XHTML, but some testing shows that they're
//   basically ignored. So you need to use server-side imagemaps if you want to have a form
//   where you are choosing a verb from (for example) a <SELECT> and also specifying part of
//   an image with an IMAGE control.
//
//
class HTML_ImageMap_Area
{
	var $href;
	var $name;
	var $id;
	var $alt;
	var $z;
	var $extrahtml;

	function common_html()
	{
		$h = "";
		if($this->name != "")
		{
			// $h .= " alt=\"".$this->name."\" ";
			$h .= " id=\"".$this->name."\" ";
		}
		if($this->href != "")
		{
			$h .= " href=\"".$this->href."\" ";
		}
		else { $h .= " nohref "; }
		if($this->extrahtml != "")
		{
			$h .= " ".$this->extrahtml." ";
		}
		return $h;
	}

}

class HTML_ImageMap_Area_Polygon extends HTML_ImageMap_Area
{
	var $points = array();
	var $minx,$maxx,$miny,$maxy; // bounding box
	var $npoints;

	function asHTML()
	{
		foreach ($this->points as $point)
		{
			$flatpoints[] = $point[0];
			$flatpoints[] = $point[1];
		}
		$coordstring = join(",",$flatpoints);

		return '<area'.$this->common_html().' shape="poly" coords="'.$coordstring.'" />';
	}

	function asJSON()
	{
		$json = "{ \"shape\":'poly', \"npoints\":".$this->npoints.", \"name\":'".$this->name."',";

		$xlist = '';
		$ylist = '';
		foreach ($this->points as $point)
		{
			$xlist .= $point[0].",";
			$ylist .= $point[1].",";
		}
		$xlist = rtrim($xlist,", ");
		$ylist = rtrim($ylist,", ");
		$json .= " \"x\": [ $xlist ], \"y\":[ $ylist ], \"minx\": ".$this->minx.", \"miny\": ".$this->miny.", \"maxx\":".$this->maxx.", \"maxy\":".$this->maxy."}";

		return($json);
	}

	function hitTest($x,$y)
	{
		$c = 0;
		// do the easy bounding-box test first.
		if( ($x < $this->minx) || ($x>$this->maxx) || ($y<$this->miny) || ($y>$this->maxy))
		{
			return false;
		}

		// Algotithm from from
		// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html#The%20C%20Code
		for ($i = 0, $j = $this->npoints-1; $i < $this->npoints; $j = $i++)
		{
			// print "Checking: $i, $j\n";
			$x1 = $this->points[$i][0];
			$y1 = $this->points[$i][1];
			$x2 = $this->points[$j][0];
			$y2 = $this->points[$j][1];

			//  print "($x,$y) vs ($x1,$y1)-($x2,$y2)\n";

			if (((($y1<=$y) && ($y<$y2)) || (($y2<=$y) && ($y<$y1))) &&
				($x < ($x2 - $x1) * ($y - $y1) / ($y2 - $y1) + $x1))
			{
				$c = !$c;
			}
		}

		return ($c);
	}

	function HTML_ImageMap_Area_Polygon ( $name="", $href="",$coords)
	{
		$c = $coords[0];

		$this->name = $name;
		$this->href= $href;
		$this->npoints = count($c)/2;

		if( intval($this->npoints) != ($this->npoints))
		{
			die("Odd number of points!");
		}

		for ($i=0; $i<count($c); $i+=2)
		{
			$x = round($c[$i]);
			$y = round($c[$i+1]);
			$point = array($x,$y);
			$xlist[] = $x; // these two are used to get the bounding box in a moment
			$ylist[] = $y;
			$this->points[] = $point;
		}

		$this->minx = min($xlist);
		$this->maxx = max($xlist);
		$this->miny = min($ylist);
		$this->maxy = max($ylist);

		//        print $this->asHTML()."\n";
	}

}

class HTML_ImageMap_Area_Rectangle extends HTML_ImageMap_Area
{
	var $x1,$x2,$y1,$y2;

	function HTML_ImageMap_Area_Rectangle ( $name="", $href="",$coords)
	{

		$c = $coords[0];

		$x1 = round($c[0]);
		$y1 = round($c[1]);
		$x2 = round($c[2]);
		$y2 = round($c[3]);

		// sort the points, so that the first is the top-left
		if($x1>$x2)
		{
			$this->x1=$x2;
			$this->x2=$x1;
		}
		else
		{
			$this->x1=$x1;
			$this->x2=$x2;
		}

		if($y1>$y2)
		{
			$this->y1=$y2;
			$this->y2=$y1;
		}
		else
		{
			$this->y1=$y1;
			$this->y2=$y2;
		}

		$this->name = $name;
		$this->href = $href;
	}

	function hitTest($x,$y)
	{
		return  ( ($x > $this->x1) && ($x < $this->x2) && ($y > $this->y1) && ($y < $this->y2) );
	}

	function asHTML()
	{
		$coordstring = join(",",array($this->x1,$this->y1,$this->x2,$this->y2));
		return '<area'.$this->common_html().' shape="rect" coords="'.$coordstring.'" />';

	}

	function asJSON()
	{
		$json = "{ \"shape\":'rect', ";

		$json .= " \"x1\":".$this->x1.", \"y1\":".$this->y1.", \"x2\":".$this->x2.", \"y2\":".$this->y2.", \"name\":'".$this->name."'}";

		return($json);
	}

}

class HTML_ImageMap_Area_Circle extends HTML_ImageMap_Area
{
	var $centx,$centy, $edgex, $edgey;

	function asHTML()
	{
		$coordstring = join(",",array($this->centx,$this->centy,$this->edgex,$this->edgey) );
		return '<area'.$this->common_html().' shape="circle" coords="'.$coordstring.'" />';
	}

	function hitTest($x,$y)
	{
		$radius1 = ($this->edgey - $this->centy) * ($this->edgey - $this->centy)
			+ ($this->edgex - $this->centx) * ($this->edgex - $this->centx);

		$radius2 = ($this->edgey - $y) * ($this->edgey - $y)
			+ ($this->edgex - $x) * ($this->edgex - $x);

		return ($radius2 <= $radius1);
	}

	function HTML_ImageMap_Area_Circle($name="", $href="",$coords)
	{
		$c = $coords[0];

		$this->name = $name;
		$this->href = $href;
		$this->centx = round($c[0]);
		$this->centy = round($c[1]);
		$this->edgex = round($c[2]);
		$this->edgey = round($c[3]);
	}
}

class HTML_ImageMap
{
	var $shapes;
	var $nshapes;
	var $name;

	function HTML_ImageMap($name="")
	{
		$this->Reset();
		$this->name = $name;
	}

	function Reset()
	{
		$this->shapes = array();
		$this->nshapes = 0;
		$this->name = "";
	}

	// add an element to the map - takes an array with the info, in a similar way to HTML_QuickForm
	function addArea($element)
	{
		if (is_object($element) && is_subclass_of($element, 'html_imagemap_area')) {
			$elementObject = &$element;
		} else {
			$args = func_get_args();
			$className = "HTML_ImageMap_Area_".$element;
			$elementObject = new $className($args[1],$args[2],array_slice($args, 3));
		}

		$this->shapes[] =& $elementObject;
		$this->nshapes++;
		//      print $this->nshapes." shapes\n";
	}

	// do a hit-test based on the current map
	// - can be limited to only match elements whose names match the filter
	//   (e.g. pick a building, in a campus map)
	function hitTest($x,$y,$namefilter="")
	{
		$preg = '/'.$namefilter.'/';
		foreach ($this->shapes as $shape)
		{
			if($shape->hitTest($x,$y))
			{
				if( ($namefilter == "") || ( preg_match($preg,$shape->name) ) )
				{
					return $shape->name;
				}
			}
		}
		return false;
	}

	// update a property on all elements in the map that match a name
	// (use it for retro-actively adding in link information to a pre-built geometry before generating HTML)
	// returns the number of elements that were matched/changed
	function setProp($which, $what, $where)
	{
		$count = 0;
		for($i=0; $i<count($this->shapes); $i++)
		{
			// this USED to be a substring match, but that broke some things
			// and wasn't actually used as one anywhere.
			if( ($where == "") || ( $this->shapes[$i]->name==$where) )
			{
				switch($which)
				{
				case 'href':
					$this->shapes[$i]->href= $what;
					break;
				case 'extrahtml':
					$this->shapes[$i]->extrahtml= $what;
					#print "IMAGEMAP: Found $where and adding $which\n";
					break;
				}
				$count++;
			}
		}
		return $count;
	}

        // update a property on all elements in the map that match a name as a substring
        // (use it for retro-actively adding in link information to a pre-built geometry before generating HTML)
        // returns the number of elements that were matched/changed
        function setPropSub($which, $what, $where)
        {

                $count = 0;
                for($i=0; $i<count($this->shapes); $i++)
                {
                        if( ($where == "") || ( strstr($this->shapes[$i]->name,$where)!=FALSE ) )
                        {
                                switch($which)
                                {
                                case 'href':
                                        $this->shapes[$i]->href= $what;
                                        break;
                                case 'extrahtml':
                                        $this->shapes[$i]->extrahtml= $what;
                                        break;
                                }
                                $count++;
                        }
                }
                return $count;
        }

	// Return the imagemap as an HTML client-side imagemap for inclusion in a page
	function asHTML()
	{
		$html = '<map';
		if($this->name != "")
		{
			$html .= ' name="'.$this->name.'"';
		}
		$html .=">\n";
		foreach ($this->shapes as $shape)
		{
			$html .= $shape->asHTML()."\n";
			$html .= "\n";
		}
		$html .= "</map>\n";

		return $html;
	}

	function subJSON($namefilter="",$reverseorder=false)
	{
		$json = '';

		$preg = '/'.$namefilter.'/';
		foreach ($this->shapes as $shape)
		{
			if( ($namefilter == "") || ( preg_match($preg,$shape->name) ))
			{
				if($reverseorder)
				{
					$json = $shape->asJSON().",\n".$json;
				}
				else
				{
					$json .= $shape->asJSON().",\n";
				}
			}
		}
		$json = rtrim($json,"\n, ");
		$json .= "\n";

		return $json;
	}

	// return HTML for a subset of the map, specified by the filter string
	// (suppose you want some partof your UI to have precedence over another part
	//  - the imagemap is checked from top-to-bottom in the HTML)
	// - skipnolinks -> in normal HTML output, we don't need areas for things with no href
	function subHTML($namefilter="",$reverseorder=false, $skipnolinks=false)
	{
		$html = "";
		$preg = '/'.$namefilter.'/';
		
		foreach ($this->shapes as $shape)
		{
			# if( ($namefilter == "") || ( preg_match($preg,$shape->name) ))
			if( ($namefilter == "") || ( strstr($shape->name, $namefilter) !== FALSE ))
			{
				if(!$skipnolinks || $shape->href != "" || $shape->extrahtml != "" )
				{
					if($reverseorder)
					{
						$html = $shape->asHTML()."\n".$html;
					}
					else
					{
						$html .= $shape->asHTML()."\n";
					}
				}

			}
		}
		return $html;
	}

}
// vim:ts=4:sw=4:
?>
