<?php
// HTML Report Generator
//
// used by the SAX parser to generate HTML reports from the XML report file.
//
// webtrees: Web based Family History software
// Copyright (C) 2014 webtrees development team.
//
// Derived from PhpGedView
// Copyright (C) 2002 to 2009 PGV Development Team.  All rights reserved.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

/**
 * Main WT Report Class for HTML
 */
class WT_Report_HTML extends WT_Report_Base {
	/**
	 * Cell padding
	 *
	 * @var int
	 */
	public $cPadding = 2;

	/**
	 * Cell height ratio
	 *
	 * @var float
	 */
	public $cellHeightRatio = 1.8;

	/**
	 * Current horizontal position
	 *
	 * @var int
	 */
	public $X = 0;

	/**
	 * Current vertical position
	 *
	 * @var int
	 */
	public $Y = 0;

	/**
	 * Currently used style name
	 *
	 * @var string
	 */
	public $currentStyle = '';

	/**
	 * Page number counter
	 *
	 * @var int
	 */
	public $pageN = 1;

	/**
	 * Store the page width without left and right margins
	 *
	 * In HTML, we don't need this
	 *
	 * @var int
	 */
	public $noMarginWidth = 0;

	/**
	 * Last cell height
	 *
	 * @var int
	 */
	public $lastCellHeight = 0;

	/**
	 * LTR or RTL alignement; "left" on LTR, "right" on RTL
	 * Used in <div>
	 *
	 * @var string
	 */
	public $alignRTL = 'left';

	/**
	 * LTR or RTL entity
	 *
	 * @var string
	 */
	public $entityRTL = '&lrm;';

	/**
	 * Largest Font Height is used by TextBox etc.
	 *
	 * Use this to calculate a the text height.
	 * This makes sure that the text fits into the cell/box when different font sizes are used
	 *
	 * @var int
	 */
	public $largestFontHeight = 0;

	/**
	 * Keep track of the highest Y position
	 *
	 * Used with Header div / Body div / Footer div / "addpage" / The bottom of the last image etc.
	 *
	 * @var float
	 */
	public $maxY = 0;

	public $headerElements = array();
	public $pageHeaderElements = array();
	public $footerElements = array();
	public $bodyElements = array();
	public $printedfootnotes = array();


	/**
	 * HTML Setup - WT_Report_HTML
	 */
	function setup() {
		parent::setup();

		// Setting up the correct dimensions if Portrait (default) or Landscape
		if ($this->orientation == "landscape") {
			$tmpw = $this->pagew;
			$this->pagew = $this->pageh;
			$this->pageh = $tmpw;
		}
		// Store the pagewidth without margins
		$this->noMarginWidth = (int)($this->pagew - $this->leftmargin - $this->rightmargin);
		// If RTL
		if ($this->rtl) {
			$this->alignRTL = "right";
			$this->entityRTL = "&rlm;";
		}
		// Change the default HTML font name
		$this->defaultFont = "Arial";

		if ($this->showGenText) {
			// The default style name for Generated by.... is 'genby'
			$element = new CellHTML(0, 10, 0, 'C', '', 'genby', 1, '.', '.', 0, 0, '', '', true);
			$element->addText($this->generatedby);
			$element->setUrl(parent::wt_url);
			$this->footerElements[] = $element;
		}
	}

	function addElement($element) {
		if ($this->processing=="B") {
			return $this->bodyElements[] = $element;
		} elseif ($this->processing=="H") {
			return $this->headerElements[] = $element;
		} elseif ($this->processing=="F") {
			return $this->footerElements[] = $element;
		}
	}

	function runPageHeader() {
		foreach ($this->pageHeaderElements as $element) {
			if (is_object($element)) {
				$element->render($this);
			} elseif (is_string($element) && $element=="footnotetexts") {
				$this->Footnotes();
			} elseif (is_string($element) && $element=="addpage") {
				$this->AddPage();
			}
		}
	}

	function Footnotes() {
		$this->currentStyle = "";
		if (!empty($this->printedfootnotes)) {
			foreach ($this->printedfootnotes as $element) {
				$element->renderFootnote($this);
			}
		}
	}

	function run() {
		$controller=new WT_Controller_Simple();
		$controller
			->setPageTitle($this->title)
			->pageHeader();

		// Setting up the styles
		echo '<style type="text/css">';
		foreach ($this->Styles as $class => $style) {
			echo '.', $class, ' { ';
			if ($style["font"]=="dejavusans") {
				$style["font"] = $this->defaultFont;
			}
			echo 'font-family: ', $style['font'], '; ';
			echo 'font-size: ', $style['size'], 'pt; ';
			// Case-insensitive
			if (stripos($style['style'], 'B')!==false) {
				echo 'font-weight: bold; ';
			}
			if (stripos($style['style'], 'I')!==false) {
				echo 'font-style: italic; ';
			}
			if (stripos($style['style'], 'U')!==false) {
				echo 'text-decoration: underline; ';
			}
			if (stripos($style['style'], 'D')!==false) {
				echo 'text-decoration: line-through; ';
			}
			echo '}', PHP_EOL;
		}
		unset($class, $style);
		//-- header divider
		echo '</style>', PHP_EOL;
		echo '<div id="headermargin" style="position: relative; top: auto; height: ', $this->headermargin, 'pt; width: ', $this->noMarginWidth, 'pt;"></div>';
		echo '<div id="headerdiv" style="position: relative; top: auto; width: ', $this->noMarginWidth, 'pt;">';
		foreach ($this->headerElements as $element) {
			if (is_object($element)) {
				$element->render($this);
			} elseif (is_string($element) && $element=="footnotetexts") {
				$this->Footnotes();
			} elseif (is_string($element) && $element=="addpage") {
				$this->AddPage();
			}
		}
		//-- body
		echo '</div>';
		echo '<script>document.getElementById("headerdiv").style.height="', $this->topmargin - $this->headermargin - 6, 'pt";</script>';
		echo '<div id="bodydiv" style="position: relative; top: auto; width: ', $this->noMarginWidth, 'pt; height: 100%;">';
		$this->Y = 0;
		$this->maxY = 0;
		$this->runPageHeader();
		foreach ($this->bodyElements as $element) {
			if (is_object($element)) {
				$element->render($this);
			} elseif (is_string($element) && $element=="footnotetexts") {
				$this->Footnotes();
			} elseif (is_string($element) && $element=="addpage") {
				$this->AddPage();
			}
		}
		//-- footer
		echo '</div>';
		echo '<script>document.getElementById("bodydiv").style.height="', $this->maxY, 'pt";</script>';
		echo '<div id="bottommargin" style="position: relative; top: auto; height: ', $this->bottommargin - $this->footermargin, 'pt;width:', $this->noMarginWidth, 'pt;"></div>';
		echo '<div id="footerdiv" style="position: relative; top: auto; width: ', $this->noMarginWidth, 'pt;height:auto;">';
		$this->Y = 0;
		$this->X = 0;
		$this->maxY = 0;
		foreach ($this->footerElements as $element) {
			if (is_object($element)) {
				$element->render($this);
			} elseif (is_string($element) && $element=="footnotetexts") {
				$this->Footnotes();
			} elseif (is_string($element) && $element=="addpage") {
				$this->AddPage();
			}
		}
		echo '</div>';
		echo '<script>document.getElementById("footerdiv").style.height="', $this->maxY, 'pt";</script>';
		echo '<div id="footermargin" style="position: relative; top: auto; height: ', $this->footermargin, 'pt;width:', $this->noMarginWidth, 'pt;"></div>';
	}

	/**
	 * Create a new Cell object - WT_Report_HTML
	 *
	 * @param int     $width   cell width (expressed in points)
	 * @param int     $height  cell height (expressed in points)
	 * @param mixed   $border  Border style
	 * @param string  $align   Text alignement
	 * @param string  $bgcolor Background color code
	 * @param string  $style   The name of the text style
	 * @param int     $ln      Indicates where the current position should go after the call
	 * @param mixed   $top     Y-position
	 * @param mixed   $left    X-position
	 * @param int     $fill    Indicates if the cell background must be painted (1) or transparent (0). Default value: 0.
	 * @param int     $stretch Stretch carachter mode
	 * @param string  $bocolor Border color
	 * @param string  $tcolor  Text color
	 * @param boolean $reseth
	 *
	 * @return object CellHTML
	 */
	function createCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth) {
		return new CellHTML($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth);
	}

	function createTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth) {
		return new TextBoxHTML($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth);
	}

	function createText($style, $color) {
		return new TextHTML($style, $color);
	}

	function createFootnote($style="") {
		return new FootnoteHTML($style);
	}

	function createPageHeader() {
		return new PageHeaderHTML();
	}

	function createImage($file, $x, $y, $w, $h, $align, $ln) {
		return new ImageHTML($file, $x, $y, $w, $h, $align, $ln);
	}

	function createImageFromObject($mediaobject, $x, $y, $w, $h, $align, $ln) {
		return new ImageHTML($mediaobject->getHtmlUrlDirect('thumb'), $x, $y, $w, $h, $align, $ln);
	}

	function createLine($x1, $y1, $x2, $y2) {
		return new LineHTML($x1, $y1, $x2, $y2);
	}

	function createHTML($tag, $attrs) {
		return new HtmlHTML($tag, $attrs);
	}

	/**
	 * Clear the Header - WT_Report_HTML
	 */
	function clearHeader() {
		$this->headerElements = array();
	}

	/****************************
	 * Local HTML Report functions
	 ****************************/

	/**
	 * Update the Page Number and set a new Y if max Y is larger - WT_Report_HTML
	 */
	function AddPage() {
		//echo("\n\n<p style=\"page-break-before:always;\"><p>\n");
		$this->pageN++;
		// Add a little margin to max Y "between pages"
		$this->maxY += 10;
		// If Y is still heigher by any reason...
		if ($this->maxY < $this->Y) {
			// ... update max Y
			$this->maxY = $this->Y;
		}
		// else update Y so that nothing will be overwritten, like images or cells...
		else {
			$this->Y = $this->maxY;
		}
	}

	/**
	 * Uppdate max Y to keep track it incase of a pagebreak - WT_Report_HTML
	 *
	 * @param float $y
	 */
	function addMaxY($y) {
		if ($this->maxY < $y) {
			$this->maxY = $y;
		}
	}

	function addPageHeader($element) {
		$this->pageHeaderElements[] = $element;
		return count($this->headerElements)-1;
	}

	/**
	 * Checks the Footnote and numbers them - WT_Report_HTML
	 *
	 * @param object $footnote
	 *
	 * @return boolean false if not numbered before | object if already numbered
	 */
	function checkFootnote($footnote) {
		$ct = count($this->printedfootnotes);
		$i = 0;
		$val = $footnote->getValue();
		while ($i < $ct) {
			if ($this->printedfootnotes[$i]->getValue() == $val) {
				// If this footnote already exist then set up the numbers for this object
				$footnote->setNum($i + 1);
				$footnote->setAddlink($i + 1);
				return $this->printedfootnotes[$i];
			}
			$i++;
		}
		// If this Footnote has not been set up yet
		$footnote->setNum($ct + 1);
		$footnote->setAddlink($ct + 1);
		$this->printedfootnotes[] = $footnote;
		return false;
	}

	/**
	 * Clear the Page Header - WT_Report_HTML
	 */
	function clearPageHeader() {
		$this->pageHeaderElements = array();
	}

	/**
	 * Count the number of lines - WT_Report_HTML
	 *
	 * @param string $str
	 *
	 * @return int Number of lines. 0 if empty line
	 */
	function countLines($str) {
		if ($str == "") {
			return 0;
		}
		return (substr_count($str, "\n") + 1);
	}

	function getCurrentStyle() {
		return $this->currentStyle;
	}

	function getCurrentStyleHeight() {
		if (empty($this->currentStyle)) {
			return $this->defaultFontSize;
		}
		$style = $this->getStyle($this->currentStyle);
		return $style["size"];
	}

	function getFootnotesHeight($cellWidth) {
		$h = 0;
		foreach ($this->printedfootnotes as $element) {
			$h += $element->getFootnoteHeight($this, $cellWidth);
		}
		return $h;
	}

	/**
	 * Get the maximum width from current position to the margin - WT_Report_HTML
	 *
	 * @return float
	 */
	function getRemainingWidth() {
		return (int)($this->noMarginWidth - $this->X);
	}

	function getPageHeight() {
		return $this->pageh - $this->topmargin;
	}

	function getStringWidth($text) {
		$style = $this->getStyle($this->currentStyle);
		return utf8_strlen($text) * ($style["size"]/2);
	}

	/**
	 * Get a text height in points - WT_Report_HTML
	 *
	 * @param $str
	 *
	 * @return int
	 */
	function getTextCellHeight($str) {
		// Count the number of lines to calculate the height
		$nl = $this->countLines($str);
		// Calculate the cell height
		return ceil(($this->getCurrentStyleHeight() * $this->cellHeightRatio) * $nl);
	}

	/**
	 * Get the current X position - WT_Report_HTML
	 *
	 * @return float
	 */
	function GetX() {
		return $this->X;
	}

	/**
	 * Get the current Y position - WT_Report_HTML
	 *
	 * @return float
	 */
	function GetY() {
		return $this->Y;
	}

	/**
	 * Get the current page number - WT_Report_HTML
	 *
	 * @return int
	 */
	function PageNo() {
		return $this->pageN;
	}

	function setCurrentStyle($s) {
		$this->currentStyle = $s;
	}

	/**
	 * Set the X position - WT_Report_HTML
	 *
	 * @param float $x
	 */
	function SetX($x) {
		$this->X = $x;
	}

	/**
	 * Set the Y position - WT_Report_HTML
	 *
	 * Also updates Max Y position
	 *
	 * @param float $y
	 */
	function SetY($y) {
		$this->Y = $y;
		if ($this->maxY < $y) {
			$this->maxY = $y;
		}
	}

	/**
	 * Set the X and Y position - WT_Report_HTML
	 *
	 * Also updates Max Y position
	 *
	 * @param float $x
	 * @param float $y
	 */
	function SetXY($x, $y) {
		$this->X = $x;
		// Don't reinvent the wheel, use this instead
		$this->SetY($y);
	}

	/**
	 * Wrap text - WT_Report_HTML
	 *
	 * @param string $str  Text to wrap
	 * @param int    $width Width in points the text has to fit into
	 *
	 * @return string
	 */
	function textWrap($str, $width) {
		// Calculate the line width
		$lw = (int)($width / ($this->getCurrentStyleHeight() / 2));
		// Wordwrap each line
		//@@ indi source texts, note text, indi sub-titles, footer texts
		$lines = explode("\n", $str);
		// Line Feed counter
		$lfct = count($lines);
		$wraptext = '';
		foreach ($lines as $line) {
			$wtext = utf8_wordwrap($line, $lw, "\n", true);
			$wraptext .= $wtext;
			// Add a new line as long as it’s not the last line
			if ($lfct > 1) {
				$wraptext.= "\n";
			}
			$lfct--;
		}
		return $wraptext;
	}

	/**
	 * Write text - WT_Report_HTML
	 *
	 * @param string $text  Text to print
	 * @param string $color HTML RGB color code (Ex: #001122)
	 * @param bool   $useclass
	 */
	function write($text, $color="", $useclass=true) {
		global $TEXT_DIRECTION;

		$style = $this->getStyle($this->getCurrentStyle());
		$htmlcode = "<span dir=\"$TEXT_DIRECTION\"";
		if ($useclass) {
			$htmlcode .= " class=\"". $style["name"]. "\"";
		}
		if (!empty($color)) {
			// Check if Text Color is set and if it’s valid HTML color
			if (preg_match("/#?(..)(..)(..)/", $color)) {
				$htmlcode .= " style=\"color:$color;\"";
			}
		}

		$htmlcode .= ">$text</span>";
 		$htmlcode = str_replace(array("\n", "> ", " <"), array("<br>", ">&nbsp;", "&nbsp;<"), $htmlcode);
		echo $htmlcode;
	}

} //-- end Report

/**
 * Cell element - HTML
 */
class CellHTML extends Cell {
	/**
	 * HTML Cell renderer
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return void
	 */
	function render($html) {
		//@@ report title, indi report sub-titles, total indis, generated by & date, indi note text	- why is the text printed on 2 lines?? What adds \n?

		if (strpos($this->text, "{{:ptp:}}") !== false) {
			return;
		}
		$temptext = str_replace("#PAGENUM#", $html->PageNo(), $this->text);
		// underline «title» part of Source item
		$temptext = str_replace(array('«', '»'), array('<u>', '</u>'), $temptext);

		// Setup the style name
		if ($html->getCurrentStyle() != $this->styleName) {
			$html->setCurrentStyle($this->styleName);
		}

		/**
		 * Keep the original values, use these local variables
		 */
		$cW = 0; // Class Width
		$cP = 0; // Class Padding

		// If (Future-feature-enable/disable cell padding)
		$cP = $html->cPadding;

		// Adjust the positions
		if ($this->left == ".") {
			$this->left = $html->GetX();
		} else {
			$html->SetX($this->left);
		}

		if ($this->top == ".") {
			$this->top = $html->GetY();
		} else {
			$html->SetY($this->top);
		}

		// Start collecting the HTML code
		echo "<div class=\"", $this->styleName, "\" style=\"position:absolute;top:", $this->top, "pt;";
		// Use Cell around padding to support RTL also
		echo "padding:", $cP, "pt;";
		// LTR (left) or RTL (right)
		echo $html->alignRTL, ":", $this->left, "pt;";
		// Background color
		if (!empty($this->bgcolor)) {
			echo "background-color:", $this->bgcolor, ";";
		}
		// Border setup
		$bpixX = 0;
		$bpixY = 0;
		if (!empty($this->border)) {
			// Border all around
			if ($this->border == 1) {
				echo " border:solid ";
				if (!empty($this->bocolor)) {
					echo $this->bocolor;
				} else {
					echo "black";
				}
				echo " 1pt;";
				$bpixX = 1;
				$bpixY = 1;
			} else {
				if (stripos($this->border, "T") !== false) {
					echo " border-top:solid ";
					if (!empty($this->bocolor)) {
						echo $this->bocolor;
					} else {
						echo "black";
					}
					echo " 1pt;";
					$bpixY = 1;
				}
				if (stripos($this->border, "B") !== false) {
					echo " border-bottom:solid ";
					if (!empty($this->bocolor)) {
						echo $this->bocolor;
					} else {
						echo "black";
					}
					echo " 1pt;";
					$bpixY = 1;
				}
				if (stripos($this->border, "R") !== false) {
					echo " border-right:solid ";
					if (!empty($this->bocolor)) {
						echo $this->bocolor;
					} else {
						echo "black";
					}
					echo " 1pt;";
					$bpixX = 1;
				}
				if (stripos($this->border, "L") !== false) {
					echo " border-left:solid ";
					if (!empty($this->bocolor)) {
						echo $this->bocolor;
					} else {
						echo "black";
					}
					echo " 1pt;";
					$bpixX = 1;
				}
			}
		}
		// Check the width if set to page wide OR set by xml to larger then page wide
		if (($this->width == 0) or $this->width > $html->getRemainingWidth()) {
			$this->width = $html->getRemainingWidth();
		}
		// We have to calculate a different width for the padding, counting on both side
		$cW = $this->width - ($cP * 2);

		// If there is any text
		if (!empty($temptext)) {
			// Wrap the text
			$temptext = $html->textWrap($temptext, $cW);
			$tmph = $html->getTextCellHeight($temptext);
			// Add some cell padding
			$this->height += $cP;
			if ($tmph > $this->height) {
				$this->height = $tmph;
			}
		}
		// Check the last cell height and ajust with the current cell height
		if ($html->lastCellHeight > $this->height) {
			$this->height = $html->lastCellHeight;
		}
		echo " width:", $cW - $bpixX, "pt;height:", $this->height - $bpixY, "pt;";

		// Text alignment
		switch($this->align) {
			case "C":
				echo " text-align:center;";
				break;
			case "L":
				echo " text-align:left;";
				break;
			case "R":
				echo " text-align:right;";
				break;
		}

		// Print the collected HTML code
		echo "\">";

		// Print URL
		if (!empty($this->url)) {
			echo "<a href=\"", $this->url, "\">";
		}
		// Print any text if exists
		if (!empty($temptext)) {
//$temptext.="##"; //@@@
			$html->write($temptext, $this->tcolor, false);
		}
		if (!empty($this->url)) {
			echo "</a>";
		}
		// Finish the cell printing and start to clean up
		echo "</div>\n";
		// Where to place the next position
		// -> Next to this cell in the same line
		if ($this->newline == 0) {
			$html->SetXY($this->left + $this->width, $this->top);
			$html->lastCellHeight = $this->height;
		}
		// -> On a new line at the margin - Default
		elseif ($this->newline == 1) {
			$html->SetXY(0, $html->GetY() + $this->height + ($cP * 2));
			// Reset the last cell height for the next line
			$html->lastCellHeight = 0;
		}
		// -> On a new line at the end of this cell
		elseif ($this->newline == 2) {
			$html->SetXY($html->GetX() + $this->width, $html->GetY() + $this->height + ($cP * 2));
			// Reset the last cell height for the next line
			$html->lastCellHeight = 0;
		}
	}
}

/**
 * HTML element - HTML Report
 */
class HtmlHTML extends Html {
	function render($html, $sub = false, $inat=true) {

		if (!empty($this->attrs["wt_style"])) $html->setCurrentStyle($this->attrs["wt_style"]);

		$this->text = $this->getStart(). $this->text;
		foreach ($this->elements as $element) {
			if (is_string($element) && $element=="footnotetexts") {
				$html->Footnotes();
			} elseif (is_string($element) && $element=="addpage") {
				$html->AddPage();
			} elseif ($element->get_type()=="Html") {
				$this->text .= $element->render($html, true, false);
			} else {
				$element->render($html);
			}
		}
		$this->text .= $this->getEnd();
		if ($sub) return $this->text;

		// If not called by an other attribute
		if ($inat) {
			$startX = $html->GetX();
			$startY = $html->GetY();
			$width = $html->getRemainingWidth();
			echo "<div style=\"position: absolute;top: ", $startY, "pt;", $html->alignRTL, ": ", $startX, "pt;width: ", $width, "pt;\">";
			$startY += $html->getCurrentStyleHeight() + 2;
			$html->SetY($startY);
		}

		echo $this->text;

		if ($inat) {
			echo "</div>\n";
		}
	}
}

/**
 * TextBox element - HTML Report
 */
class TextBoxHTML extends TextBox {
	function render($html) {
		// checkFootnote
		$newelements = array();
		$lastelement = array();
		$footnote_element = array();
		// Element counter
		$cE = count($this->elements);
		//-- collapse duplicate elements
		for ($i = 0; $i < $cE; $i++) {
			$element = $this->elements[$i];
			if (is_object($element)) {
				if ($element->get_type() == "Text") {
					if (!empty($footnote_element)) {
						ksort($footnote_element);
						foreach ($footnote_element as $links) {
							$newelements[] = $links;
						}
						$footnote_element = array();
					}
					if (empty($lastelement)) {
						$lastelement = $element;
					} else {
						// Checking if the Text has the same style
						if ($element->getStyleName() == $lastelement->getStyleName()) {
							$lastelement->addText(str_replace("\n", "<br>", $element->getValue()));
						} elseif (!empty($lastelement)) {
							$newelements[] = $lastelement;
							$lastelement = $element;
						}
					}
				}
				// Collect the Footnote links
				elseif ($element->get_type() == "Footnote") {
					// Check if the Footnote has been set with it’s link number
					$html->checkFootnote($element);
					// Save first the last element if any
					if (!empty($lastelement)) {
						$newelements[] = $lastelement;
						$lastelement = array();
					}
					// Save the Footnote with it’s link number as key for sorting later
					$footnote_element[$element->num] = $element;
				}
				//-- do not keep empty footnotes
				elseif (($element->get_type() != "Footnote") || (trim($element->getValue()) != "")) {
					if (!empty($footnote_element)) {
						ksort($footnote_element);
						foreach ($footnote_element as $links) {
							$newelements[] = $links;
						}
						$footnote_element = array();
					}
					if (!empty($lastelement)) {
						$newelements[] = $lastelement;
						$lastelement = array();
					}
					$newelements[] = $element;
				}
			} else {
				if (!empty($lastelement)) {
					$newelements[] = $lastelement;
					$lastelement = array();
				}
				if (!empty($footnote_element)) {
					ksort($footnote_element);
					foreach ($footnote_element as $links) {
						$newelements[] = $links;
					}
					$footnote_element = array();
				}
				$newelements[] = $element;
			}
		}
		if (!empty($lastelement)) {
			$newelements[] = $lastelement;
		}
		if (!empty($footnote_element)) {
			ksort($footnote_element);
			foreach ($footnote_element as $links) {
				$newelements[] = $links;
			}
		}
		$this->elements = $newelements;
		unset($footnote_element, $lastelement, $links, $newelements);

		/**
		 * Use these variables to update/manipulate values
		 * Repeted classes would reupdate all their class variables again, Header/Page Header/Footer
		 * This is the bugfree version
		 */
		$cH = 0; // Class Height
		$cX = 0; // Class Left
		// Protect height, width, lastheight from padding
		$cP = 0; // Class Padding
		$cW = 0; // Class Width
		// Used with line breaks and cell height calculation within this box only
		$html->largestFontHeight = 0;

		// Current position
		if ($this->left == ".") {
			$cX = $html->GetX();
		} else {
			$cX = $this->left;
			$html->SetX($cX);
		}
		// Current position (top)
		if ($this->top == ".") {
			$this->top = $html->GetY();
		} else {
			$html->SetY($this->top);
		}

		// Check the width if set to page wide OR set by xml to larger then page wide
		if (($this->width == 0) or ($this->width > $html->getRemainingWidth())) {
			$this->width = $html->getRemainingWidth();
		}
		// Setup the CellPadding
		if ($this->padding) {
			$cP = $html->cPadding;
		}

		// For padding, we have to use less wrap width
		$cW = $this->width - ($cP * 2);

		//-- calculate the text box height
		// Number of lines, will be converted to height
		$cHT = 0;
		// Element height (exept text)
		$eH = 0;
		// Footnote height (in points)
		$fH = 0;
		$w = 0;
		//-- $lw is an array
		// 0 => last line width
		// 1 => 1 if text was wrapped, 0 if text did not wrap
		// 2 => number of LF
		$lw = array();
		// Element counter
		$cE = count($this->elements);
		for ($i = 0; $i < $cE; $i++) {
			if (is_object($this->elements[$i])) {
				$ew = $this->elements[$i]->setWrapWidth($cW - $w - 2, $cW);
				if ($ew == $cW)
					$w = 0;
				$lw = $this->elements[$i]->getWidth($html);
				// Text is already gets the # LF
				$cHT += $lw[2];
				if ($lw[1] == 1) {
					$w = $lw[0];
				} elseif ($lw[1] == 2) {
					$w = 0;
				} else {
					$w += $lw[0];
				}
				if ($w > $cW) {
					$w = $lw[0];
				}
				// For anything else but text (images), get the height
				$eH += $this->elements[$i]->getHeight($html);
			} else {
				//if (is_string($element) and $element == "footnotetexts") $html->Footnotes();
				$fH += abs($html->getFootnotesHeight($cW));
			}
		}
		// Add up what’s the final height
		$cH = $this->height;
		// If any element exist
		if ($cE > 0) {
			// Check if this is text or some other element, like images
			if ($eH == 0) {
				// Number of LF but at least one line
				$cHT = ($cHT + 1) * $html->cellHeightRatio;
				// Calculate the cell hight with the largest font size used
				$cHT = $cHT * $html->largestFontHeight;
				if ($cH < $cHT) {
					$cH = $cHT;
				}
			}
			// This is any other element
			else {
				if ($cH < $eH) {
					$cH = $eH;
				}
				// Add Footnote height to the rest of the height
				$cH += $fH;
			}
		}

		unset($lw, $cHT, $fH, $w);

		// Finaly, check the last cells height
		if ($cH < $html->lastCellHeight) {
			$cH = $html->lastCellHeight;
		}
		// Update max Y incase of a pagebreak
		// We don't want to over write any images or other stuff
		$html->addMaxY($this->top + $cH);

		// Start to print HTML
		echo "<div style=\"position:absolute;top:", $this->top, "pt;";
		// LTR (left) or RTL (right)
		echo $html->alignRTL, ":", $cX, "pt;";
		// Background color
		if ($this->fill) {
			if (!empty($this->bgcolor)) {
				echo " background-color:", $this->bgcolor, ";";
			}
		}
		// Print padding only when it’s set
		if ($this->padding) {
			// Use Cell around padding to support RTL also
			echo "padding:", $cP, "pt;";
		}
		// Border setup
		if ($this->border) {
			echo " border:solid black 1pt;";
			echo "width:", ($this->width - 1 - ($cP * 2)), "pt;height:", $cH - 1, "pt;";
		} else {
			echo "width:", ($this->width - ($cP * 2)), "pt;height:", $cH, "pt;";
		}
		echo "\">";

		// Do a little "margin" trick before print
		// to get the correct current position => "."
		$cXT = $html->GetX();
		$cYT = $html->GetY();
		$html->SetXY(0, 0);

		// Print the text elements
		foreach ($this->elements as $element) {
			if (is_object($element)) {
				$element->render($html, $cX, false);
			} elseif (is_string($element) && $element=="footnotetexts") {
				$html->Footnotes();
			} elseif (is_string($element) && $element == "addpage") {
				$html->AddPage();
			}
		}
		//@@ address report - table titles and data, 	indi report end of source list
		echo "</div>\n";

		// Reset "margins"
		$html->SetXY($cXT, $cYT);
		// This will be mostly used to trick the multiple images last height
		if ($this->reseth) {
			$cH = 0;
		}
		// New line and some clean up
		if (!$this->newline) {
			$html->SetXY($cX + $this->width, $this->top);
			$html->lastCellHeight = $cH;
		} else {
			$html->SetXY(0, $this->top + $cH + ($cP * 2));
			$html->lastCellHeight = 0;
		}
	}
}

/**
 * Text element - HTML Report
 */
class TextHTML extends Text {
	/**
	 * @param WT_Report_HTML $html
	 * @param int            $curx
	 * @param boolean        $attrib Is is called from a different element?
	 *
	 * @return void
	 */
	function render($html, $curx=0, $attrib=true) {

		// Setup the style name
		if ($html->getCurrentStyle() != $this->styleName) {
			$html->setCurrentStyle($this->styleName);
		}
		$temptext = str_replace("#PAGENUM#", $html->PageNo(), $this->text);
		// underline «title» part of Source item
		$temptext = str_replace(array('«', '»'), array('<u>', '</u>'), $temptext);

		// If any text at all
		if (!empty($temptext)) {
			// If called by an other element
			if (!$attrib) {
				$html->write($temptext, $this->color);
				//@@ Address table titles & data, indi & fam name, data & Sources title,
			} else {
				// Save the start positions
				$startX = $html->GetX();
				$startY = $html->GetY();
				$width = $html->getRemainingWidth();
				// If text is wider then page width then wrap it
				if ($html->GetStringWidth($temptext) > $width) {
					$lines = explode("\n", $temptext);
					foreach ($lines as $line) {
						echo "<div style=\"position:absolute;top:", $startY, "pt;", $html->alignRTL, ":", $startX, "pt;width:", $width, "pt;\">";
						$line = $html->textWrap($line, $width);
						$startY += $html->getTextCellHeight($line);
						$html->SetY($startY);
						$html->write($line, $this->color);
						echo "</div>\n";
					}
				} else {
					echo "<div style=\"position:absolute;top:", $startY, "pt;", $html->alignRTL, ":", $startX, "pt;width:", $width, "pt;\">";
					$html->write($temptext, $this->color);
					echo "</div>\n";
					$html->SetX($startX + $html->GetStringWidth($temptext));
					if ($html->countLines($temptext) != 1) {
						$html->SetXY(0, ($startY + $html->getTextCellHeight($temptext)));
					}
				}
			}
		}
	}

	/**
	 * Returns the height in points of the text element
	 *
	 * The height is already calculated in getWidth()
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return float
	 */
	function getHeight($html) {
		$ct = substr_count($this->text, "\n");
		if ($ct>0) {
			$ct += 1;
		}
		$style = $html->getStyle($this->styleName);
		return ($style["size"] * $ct) * $html->cellHeightRatio;
	}

	/**
	 * Get the width of text and wrap it too
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return array
	 */
	function getWidth($html) {
		// Setup the style name
		if ($html->getCurrentStyle() != $this->styleName) {
			$html->setCurrentStyle($this->styleName);
		}

		// Check for the largest font size in the box
		$fsize = $html->getCurrentStyleHeight();
		if ($fsize > $html->largestFontHeight) {
			$html->largestFontHeight = $fsize;
		}

		// Get the line width for the text in points
		$lw = $html->GetStringWidth($this->text);
		// Line Feed counter - Number of lines in the text
		$lfct = $html->countLines($this->text);
		// If there is still remaining wrap width...
		if ($this->wrapWidthRemaining > 0) {
			// Check with line counter too!
			if (($lw >= $this->wrapWidthRemaining) or ($lfct > 1)) {
				$newtext = "";
				$wrapWidthRemaining = $this->wrapWidthRemaining;
				$lines = explode("\n", $this->text);
				// Go throught the text line by line
				foreach ($lines as $line) {
					// Line width in points + a little margin
					$lw = $html->GetStringWidth($line);
					// If the line has to be wraped
					if ($lw > $wrapWidthRemaining) {
						$words = explode(" ", $line);
						$addspace = count($words);
						$lw = 0;
						foreach ($words as $word) {
							$addspace--;
							$lw += $html->GetStringWidth($word." ");
							if ($lw <= $wrapWidthRemaining) {
								$newtext .= $word;
								if ($addspace != 0) {
									$newtext .= " ";
								}
							} else {
								$lw = $html->GetStringWidth($word." ");
								$newtext .= "\n$word";
								if ($addspace != 0) {
									$newtext .= " ";
								}
								// Reset the wrap width to the cell width
								$wrapWidthRemaining = $this->wrapWidthCell;
							}
						}
					} else {
						$newtext .= $line;
					}
					// Check the Line Feed counter
					if ($lfct > 1) {
						// Add a new line feed as long as it’s not the last line
						$newtext.= "\n";
						// Reset the line width
						$lw = 0;
						// Reset the wrap width to the cell width
						$wrapWidthRemaining = $this->wrapWidthCell;
					}
					$lfct--;
				}
				$this->text = $newtext;
				$lfct = substr_count($this->text, "\n");
				return array($lw, 1, $lfct);
			}
		}
		$l = 0;
		$lfct = substr_count($this->text, "\n");
		if ($lfct > 0) {
			$l = 2;
		}
		return array($lw, $l, $lfct);
	}
}

/**
 * Footnote element
 */
class FootnoteHTML extends Footnote {
	/**
	 * HTML Footnotes number renderer
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return void
	 */
	function render($html) {
		$html->setCurrentStyle("footnotenum");
		// 'vertical-align:super' is not working properly in IE 8
		// @deprecated
		echo "<a href=\"#footnote", $this->num, "\"><sup>";
		// working
		$html->write($html->entityRTL. $this->num);
		//@@ Source numbers
		echo "</sup></a>\n";
	}

	/**
	 * Write the Footnote text
	 *
	 * Uses style name "footnote" by default
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return void
	 */
	function renderFootnote($html) {

		if ($html->getCurrentStyle() != $this->styleName) {
			$html->setCurrentStyle($this->styleName);
		}

		$temptext = str_replace("#PAGENUM#", $html->PageNo(), $this->text);
		// underline «title» part of Source item
		$temptext = str_replace(array('«', '»'), array('<u>', '</u>'), $temptext);
		echo "\n<div><a name=\"footnote", $this->num, "\"></a>";
		$html->write($this->num. ". ". $temptext);
		echo "</div>";

		$html->SetXY(0, $html->GetY() + $this->getFootnoteHeight($html));
	}

	/**
	 * Calculates the Footnotes height
	 *
	 * @param WT_Report_HTML $html
	 * @param int            $cellWidth The width of the cell to use it for text wraping
	 *
	 * @return Footnote height in points
	 */
	function getFootnoteHeight($html, $cellWidth=0) {
		if ($html->getCurrentStyle() != $this->styleName) {
			$html->setCurrentStyle($this->styleName);
		}

		if ($cellWidth > 0) {
			$this->text = $html->textWrap($this->text, $cellWidth);
		}
		$this->text = $this->text. "\n\n";
		$ct = substr_count($this->text, "\n");
		$fsize = $html->getCurrentStyleHeight();
		return ($fsize * $ct) * $html->cellHeightRatio;
	}

	/**
	 * Get the width of text
	 *
	 * Breaks up a text into lines if needed
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return array
	 */
	function getWidth($html) {
		// Setup the style name
		$html->setCurrentStyle("footnotenum");

		// Check for the largest font size in the box
		$fsize = $html->getCurrentStyleHeight();
		if ($fsize > $html->largestFontHeight) {
			$html->largestFontHeight = $fsize;
		}

		// Returns the Object if already numbered else false
		if (empty($this->num)) {
			$html->checkFootnote($this);
		}

		// Get the line width for the text in points + a little margin
		$lw = $html->GetStringWidth($this->numText);
		// Line Feed counter - Number of lines in the text
		$lfct = $html->countLines($this->numText);
		// If there is still remaining wrap width...
		if ($this->wrapWidthRemaining > 0) {
			// Check with line counter too!
			if (($lw >= $this->wrapWidthRemaining) or ($lfct > 1)) {
				$newtext = "";
				$wrapWidthRemaining = $this->wrapWidthRemaining;
				$lines = explode("\n", $this->numText);
				// Go throught the text line by line
				foreach ($lines as $line) {
					// Line width in points + a little margin
					$lw = $html->GetStringWidth($line);
					// If the line has to be wraped
					if ($lw > $wrapWidthRemaining) {
						$words = explode(" ", $line);
						$addspace = count($words);
						$lw = 0;
						foreach ($words as $word) {
							$addspace--;
							$lw += $html->GetStringWidth($word." ");
							if ($lw <= $wrapWidthRemaining) {
								$newtext .= $word;
								if ($addspace != 0) {
									$newtext .= " ";
								}
							} else {
								$lw = $html->GetStringWidth($word." ");
								$newtext .= "\n$word";
								if ($addspace != 0) {
									$newtext .= " ";
								}
								// Reset the wrap width to the cell width
								$wrapWidthRemaining = $this->wrapWidthCell;
							}
						}
					} else {
						$newtext .= $line;
					}
					// Check the Line Feed counter
					if ($lfct > 1) {
						// Add a new line feed as long as it’s not the last line
						$newtext.= "\n";
						// Reset the line width
						$lw = 0;
						// Reset the wrap width to the cell width
						$wrapWidthRemaining = $this->wrapWidthCell;
					}
					$lfct--;
				}
				$this->numText = $newtext;
				$lfct = substr_count($this->numText, "\n");
				return array($lw, 1, $lfct);
			}
		}
		$l = 0;
		$lfct = substr_count($this->numText, "\n");
		if ($lfct > 0) {
			$l = 2;
		}
		return array($lw, $l, $lfct);
	}
}

/**
 * PageHeader element
 */
class PageHeaderHTML extends PageHeader {
	function render($html) {
		$html->clearPageHeader();
		foreach ($this->elements as $element) {
			$html->addPageHeader($element);
		}
	}
}

/**
 * Image element
 */
class ImageHTML extends Image {
	/**
	 * Image renderer
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return void
	 */
	function render($html) {
		global $lastpicbottom, $lastpicpage, $lastpicleft, $lastpicright;

		// Get the current positions
		if ($this->x == ".") {
			$this->x=$html->GetX();
		}
		if ($this->y == ".") {
			//-- first check for a collision with the last picture
			if (isset($lastpicbottom)) {
				if (($html->PageNo() == $lastpicpage) && ($lastpicbottom >= $html->GetY()) && ($this->x >= $lastpicleft) && ($this->x <= $lastpicright)) {
					$html->SetY($lastpicbottom + ($html->cPadding * 2));
				}
			}
			$this->y = $html->GetY();
		}

		// Image alignment
		//@@ Indi picture
		switch($this->align) {
			case "L":
				echo "<div style=\"position:absolute;top:", $this->y, "pt;left:0pt;width:", $html->getRemainingWidth(), "pt;text-align:left;\">\n";
				echo "<img src=\"", $this->file, "\" style=\"width:", $this->width, "pt;height:", $this->height, "pt;\" alt=\"\">\n</div>\n";
				break;
			case "C":
				echo "<div style=\"position:absolute;top:", $this->y, "pt;left:0pt;width:", $html->getRemainingWidth(), "pt;text-align:center;\">\n";
				echo "<img src=\"", $this->file, "\" style=\"width:", $this->width, "pt;height:", $this->height, "pt;\" alt=\"\">\n</div>\n";
				break;
			case "R":
				echo "<div style=\"position:absolute;top:", $this->y, "pt;left:0pt;width:", $html->getRemainingWidth(), "pt;text-align:right;\">\n";
				echo "<img src=\"", $this->file, "\" style=\"width:", $this->width, "pt;height:", $this->height, "pt;\" alt=\"\">\n</div>\n";
				break;
			default:
				echo "<img src=\"", $this->file, "\" style=\"position:absolute;", $html->alignRTL, ":", $this->x, "pt;top:", $this->y, "pt;width:", $this->width, "pt;height:", $this->height, "pt;\" alt=\"\">\n";
		}

		$lastpicpage = $html->PageNo();
		$lastpicleft = $this->x;
		$lastpicright = $this->x + $this->width;
		$lastpicbottom = $this->y + $this->height;
		// Setup for the next line
		if ($this->line == "N") {
			$html->SetY($lastpicbottom);
		}
		// Keep max Y updated
		$html->addMaxY($lastpicbottom);
	}

	/**
	 * Get the image height
	 *
	 * This would be called from the TextBox only for multiple images
	 * so we add a bit bottom space between the images
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return float
	 */
	function getHeight($html) {
		return $this->height + ($html->cPadding * 2);
	}

} //-- END Image

/**
 * Line element - HTML Report
 */
class LineHTML extends Line {
	/**
	 * HTML line renderer
	 *
	 * @param WT_Report_HTML $html
	 *
	 * @return void
	 */
	function render($html) {
		if ($this->x1==".") $this->x1=$html->GetX();
		if ($this->y1==".") $this->y1=$html->GetY();
		if ($this->x2==".") {
			$this->x2 = $html->getRemainingWidth();
		}
		if ($this->y2==".") $this->y2=$html->GetY();
		// TODO Non verticle or horizontal lines can use a series of divs absolutely positioned
		// Vertical line
		if ($this->x1 == $this->x2) {
			echo "<div style=\"position:absolute;overflow:hidden;border-", $html->alignRTL, ":solid black 1pt;", $html->alignRTL, ":", $this->x1, "pt;top:", $this->y1 + 1, "pt;width:1pt;height:", $this->y2 - $this->y1, "pt;\"> </div>\n";
		}
		// Horizontal line
		if ($this->y1 == $this->y2) {
			echo "<div style=\"position:absolute;overflow:hidden;border-top:solid black 1pt;", $html->alignRTL, ":", $this->x1, "pt;top:", $this->y1 + 1, "pt;width:", $this->x2 - $this->x1, "pt;height:1pt;\"> </div>\n";
		}
		// Keep max Y updated
		// One or the other will be higher... lasy mans way...
		$html->addMaxY($this->y1);
		$html->addMaxY($this->y2);
	}
}
