<?php
/**
 * PDF Report Generator
 *
 * used by the SAX parser to generate PDF reports from the XML report file.
 *
 * webtrees: Web based Family History software
 * Copyright (C) 2010 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * @package webtrees
 * @subpackage Reports
 * @version $Id: class_reportpdf.php 9743 2010-10-30 19:40:24Z greg $
 * @link http://www.adobe.com/devnet/pdf/
 * @link http://www.tcpdf.org
 */

if (!defined('WT_WEBTREES')) {
	header('HTTP/1.0 403 Forbidden');
	exit;
}

/**
* @todo add info
*/
define('WT_CLASS_REPORTPDF_PHP', '');

require_once WT_ROOT."includes/classes/class_reportbase.php";
require_once WT_ROOT."library/tcpdf/tcpdf.php";

/**
* Main WT Report Class for PDF
*
* @package webtrees
* @subpackage Reports
*/
class ReportBasePDF extends ReportBase {
	/**
	* PDF compression - Zlib extension is required
	* @var boolean const
	*/
	const compression = true;
	/**
	* If TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
	* @var boolean const
	*/
	const diskcache = false;
	/**
	* TRUE means that the input text is unicode (PDF)
	* @var boolean const
	*/
	const unicode = true;
	/**
	* A new object of the PDF class
	* @var PDF
	*/
	public $pdf;

	/**
	* PDF Setup - ReportBasePDF
	*/
	function setup() {
		parent::setup();

		// Setup the PDF class with custom size pages because WT supports more page sizes. If WT sends an unknown size name then the default would be A4
		$this->pdf = new PDF($this->orientation, parent::unit, array($this->pagew, $this->pageh), self::unicode, $this->charset, self::diskcache);

		// Setup the PDF margins
		$this->pdf->setMargins($this->leftmargin, $this->topmargin, $this->rightmargin);
		$this->pdf->SetHeaderMargin($this->headermargin);
		$this->pdf->SetFooterMargin($this->footermargin);
		//Set auto page breaks
		$this->pdf->SetAutoPageBreak(true, $this->bottommargin);
		// Setup PDF compression
		$this->pdf->SetCompression(self::compression);
		// Setup RTL support
		$this->pdf->setRTL($this->rtl);
		// Set the document information
		// Only admin should see the version number
		$appversion = WT_WEBTREES;
		if (WT_USER_IS_ADMIN) {
			$appversion .= " ".WT_VERSION_TEXT;
		}
		$this->pdf->SetCreator($appversion." (".parent::pgv_url.")");
		// Not implemented yet - ReportBase::setup()
		//$this->pdf->SetAuthor($this->rauthor);
		$this->pdf->SetTitle($this->title);
		$this->pdf->SetSubject($this->rsubject);
		$this->pdf->SetKeywords($this->rkeywords);

		$this->pdf->setReport($this);

		if ($this->showGenText) {
			// The default style name for Generated by.... is 'genby'
			$element = new CellPDF(0, 10, 0, "C", "", "genby", 1, ".", ".", 0, 0, "", "", true);
			$element->addText($this->generatedby);
			$element->setUrl(parent::pgv_url);
			$this->pdf->addFooter($element);
		}
	}

	/**
	* Add an element - ReportBasePDF
	* @param object|string &$element Object or string
	*/
	function addElement($element) {
		if ($this->processing == "B") {
			return $this->pdf->addBody($element);
		} elseif ($this->processing == "H") {
			return $this->pdf->addHeader($element);
		} elseif ($this->processing == "F") {
			return $this->pdf->addFooter($element);
		}
		return 0;
	}

	function run() {
		$this->pdf->Body();
		header('Expires:');
		header('Pragma:');
		header('Cache-control:');
		$this->pdf->Output('webtrees_'.basename(dirname($_REQUEST['report'])).'.pdf', 'I');
	}

	/**
	* Clear the Header - ReportBasePDF
	*/
	function clearHeader() {
		$this->pdf->clearHeader();
	}

	/**
	* Clear the Page Header - ReportBasePDF
	*/
	function clearPageHeader() {
		$this->pdf->clearPageHeader();
	}

	/**
	* Create a new Cell object - ReportBasePDF
	*
	* @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: 1
	* @param int $stretch Stretch carachter mode
	* @param string $bocolor Border color
	* @param string $tcolor Text color
	* @param bolean $reseth
	* @return CellPDF
	*/
	function createCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth) {
		return new CellPDF($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth);
	}

	/**
	* Create a new TextBox object - ReportBasePDF
	*
	* @param float $width Text box width
	* @param float $height Text box height
	* @param boolean $border
	* @param string $bgcolor Background color code in HTML
	* @param boolean $newline
	* @param mixed $left
	* @param mixed $top
	* @param boolean $pagecheck
	* @param string $style
	* @param boolean $fill
	* @param boolean $padding
	* @param boolaen $reseth
	* @return TextBoxPDF
	*/
	function createTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth) {
		return new TextBoxPDF($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth);
	}

	/**
	* Create a new Text object- ReportBasePDF
	*
	* @param string $style The name of the text style
	* @param string $color HTML color code
	* @return TextPDF
	*/
	function createText($style, $color) {
		return new TextPDF($style, $color);
	}

	/**
	* Create a new Footnote object - ReportBasePDF
	* @param string $style Style name
	* @return FootnotePDF
	*/
	function createFootnote($style) {
		return new FootnotePDF($style);
	}

	/**
	* Create a new Page Header object - ReportBasePDF
	* @return PageHeaderPDF
	*/
	function createPageHeader() {
		return new PageHeaderPDF();
	}

	/**
	* Create a new image object - ReportBasePDF
	* @param string $file File name
	* @param mixed $x
	* @param mixed $y
	* @param int $w Image width
	* @param int $h Image height
	* @param string $align L:left, C:center, R:right or empty to use x/y
	* @param string $ln T:same line, N:next line
	* @return ImagePDF
	*/
	function createImage($file, $x, $y, $w, $h, $align, $ln) {
		return new ImagePDF($file, $x, $y, $w, $h, $align, $ln);
	}

	/**
	* Create a new line object - ReportBasePDF
	* @param mixed $x1
	* @param mixed $y1
	* @param mixed $x2
	* @param mixed $y2
	* @return LinePDF
	*/
	function createLine($x1, $y1, $x2, $y2) {
		return new LinePDF($x1, $y1, $x2, $y2);
	}

	/**
	* @return HtmlPDF
	*/
	function createHTML($tag, $attrs) {
		return new HtmlPDF($tag, $attrs);
	}
} //-- end Report

/**
* WT Report PDF Class
*
* This class inherits from the TCPDF class and is used to generate the PDF document
* @package webtrees
* @subpackage Reports
*/
class PDF extends TCPDF {
	/**
	* Array of elements in the header
	* @var array
	*/
	public $headerElements = array();
	/**
	* Array of elements in the page header
	* @var array
	*/
	public $pageHeaderElements = array();
	/**
	* Array of elements in the footer
	* @var array
	*/
	public $footerElements = array();
	/**
	* Array of elements in the body
	* @var array
	*/
	public $bodyElements = array();
	/**
	* Array of elements in the footer notes
	* @var array
	*/
	public $printedfootnotes = array();
	/**
	* Currently used style name
	* @var string
	*/
	public $currentStyle;
	/**
	* The last cell height
	* @var int
	*/
	public $lastCellHeight = 0;
	/**
	* The largest font size within a TextBox
	* to calculate the height
	* @var int
	*/
	public $largestFontHeight = 0;
	/**
	* The last pictures page number
	* @var int
	*/
	public $lastpicpage = 0;

	public $wt_report;

	/**
	* PDF Header -PDF
	*/
	function Header() {
		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->newPage();
			}
		}
		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->newPage();
			}
		}
	}

	/**
	* PDF Body -PDF
	*/
	function Body() {
		$this->AddPage();
		foreach ($this->bodyElements as $key => $element) {
			if (is_object($element)) {
				$element->render($this);
			} elseif (is_string($element) && $element=="footnotetexts") {
				$this->Footnotes();
			} elseif (is_string($element) && $element=="addpage") {
				$this->newPage();
			}
			// Delete used elements in hope to reduce 'some' memory usage
			unset($this->bodyElements[$key]);
		}
	}

	/**
	* PDF Footnotes -PDF
	*/
	function Footnotes() {
		foreach ($this->printedfootnotes as $element) {
			if (($this->GetY() + $element->getFootnoteHeight($this)) > $this->getPageHeight()) {
				$this->AddPage();
			}
			$element->renderFootnote($this);
			if ($this->GetY() > $this->getPageHeight()) {
				$this->AddPage();
			}
		}
	}

	/**
	* PDF Footer -PDF
	*/
	function Footer() {
		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->newPage();
			}
		}
	}

	/**
	* Add an element to the Header -PDF
	* @param object|string &$element
	* @return int The number of the Header elements
	*/
	function addHeader($element) {
		$this->headerElements[] = $element;
		return count($this->headerElements)-1;
	}

	/**
	* Add an element to the Page Header -PDF
	* @param object|string &$element
	* @return int The number of the Page Header elements
	*/
	function addPageHeader($element) {
		$this->pageHeaderElements[] = $element;
		return count($this->pageHeaderElements)-1;
	}

	/**
	* Add an element to the Body -PDF
	* @param object|string &$element
	* @return int The number of the Body elements
	*/
	function addBody($element) {
		$this->bodyElements[] = $element;
		return count($this->bodyElements)-1;
	}

	/**
	* Add an element to the Footer -PDF
	* @param object|string &$element
	* @return int The number of the Footer elements
	*/
	function addFooter($element) {
		$this->footerElements[] = $element;
		return count($this->footerElements)-1;
	}

	function removeHeader($index) {
		unset($this->headerElements[$index]);
	}

	function removePageHeader($index) {
		unset($this->pageHeaderElements[$index]);
	}

	function removeBody($index) {
		unset($this->bodyElements[$index]);
	}

	function removeFooter($index) {
		unset($this->footerElements[$index]);
	}

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

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

	function setReport($r) {
		$this->pgvreport = $r;
	}

	/**
	* Get the currently used style name -PDF
	* @return string
	*/
	function getCurrentStyle() {
		return $this->currentStyle;
	}

	/**
	* Setup a style for usage -PDF
	* @param string $s Style name
	*/
	function setCurrentStyle($s) {
		$this->currentStyle = $s;
		$style = $this->pgvreport->getStyle($s);
		$this->SetFont($style["font"], $style["style"], $style["size"]);
	}

	/**
	* Get the style -PDF
	* @param string $s Style name
	* @return array
	*/
	function getStyle($s) {
		if (!isset($this->pgvreport->Styles[$s])) {
			$s = $this->getCurrentStyle();
			$this->pgvreport->Styles[$s] = $s;
		}
		return $this->pgvreport->Styles[$s];
	}

	/**
	* Add margin when static horizontal position is used -PDF
	* RTL supported
	* @param float $x Static position
	* @return float
	*/
	function addMarginX($x) {
		$m = $this->getMargins();
		if ($this->getRTL()) {
			$x += $m["right"];
		} else {
			$x += $m["left"];
		}
		$this->SetX($x);
		return $x;
	}

	/**
	* Get the maximum line width to draw from the curren position -PDF
	* RTL supported
	* @return float
	*/
	function getMaxLineWidth() {
		$m = $this->getMargins();
		if ($this->getRTL()) {
			return ($this->getRemainingWidth() + $m["right"]);
		} else {
			return ($this->getRemainingWidth() + $m["left"]);
		}
	}

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

	/**
	* Returns the the current font size height -PDF
	* @return int
	*/
	function getCurrentStyleHeight() {
		if (empty($this->currentStyle)) {
			return $this->pgvreport->defaultFontSize;
		}
		$style = $this->pgvreport->getStyle($this->currentStyle);
		return $style["size"];
	}

	/**
	* Checks the Footnote and numbers them
	*
	* @param object &$footnote
	* @return boolen false if not numbered befor | object if already numbered
	*/
	function checkFootnote(&$footnote) {
		$ct = count($this->printedfootnotes);
		$val = $footnote->getValue();
		$i = 0;
		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($this->AddLink());
		$this->printedfootnotes[] = $footnote;
		return false;
	}

	/**
	* Used this function instead of AddPage()
	* This function will make sure that images will not be overwritten
	*/
	function newPage() {
		if ($this->lastpicpage > $this->getPage()) {
			$this->setPage($this->lastpicpage);
		}
		$this->AddPage();
	}


	/*******************************************
	* TCPDF protected functions
	*******************************************/

	/**
	* Add a page if needed -PDF
	* @param $height Cell height. Default value: 0
	* @return boolean true in case of page break, false otherwise
	*/
	function checkPageBreakPDF($height) {
		return $this->checkPageBreak($height);
	}

	/**
	* Returns the remaining width between the current position and margins -PDF
	* @return float Remaining width
	*/
	function getRemainingWidthPDF() {
		return $this->getRemainingWidth();
	}

} //-- END PDF

/**
* Report Base object of ReportBase class inherited by ReportBasePDF
* @global ReportBasePDF $wt_report
*/
$wt_report = new ReportBasePDF();

$ReportRoot = $wt_report;

/**
* Cell element - PDF
*
* @package webtrees
* @subpackage Reports
*/
class CellPDF extends Cell {
	/**
	* Create a class CELL for PDF
	*
	* @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: 1
	* @param int $stretch Stretch carachter mode
	* @param string $bocolor Border color
	* @param string $tcolor Text color
	* @param boolean $reseth
	*/
	function CellPDF($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth) {
		parent::Cell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth);
	}

	/**
	* PDF Cell renderer
	* @param PDF &$pdf
	*/
	function render(&$pdf) {
		/**
		* 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
		*/
		$cX = 0; // Class Left

		// Set up the text style
		if (($pdf->getCurrentStyle()) != ($this->styleName)) {
			$pdf->setCurrentStyle($this->styleName);
		}
		$temptext = str_replace("#PAGENUM#", $pdf->PageNo(), $this->text);

		$match = array();
		// Indicates if the cell background must be painted (1) or transparent (0)
		if ($this->fill == 1) {
			if (!empty($this->bgcolor)) {
				// HTML color to RGB
				if (preg_match("/#?(..)(..)(..)/", $this->bgcolor, $match)) {
					$r = hexdec($match[1]);
					$g = hexdec($match[2]);
					$b = hexdec($match[3]);
					$pdf->SetFillColor($r, $g, $b);
				}
			}
			// If no color set then don't fill
			else $this->fill = 0;
		}
		// Paint the Border color if set
		if (!empty($this->bocolor)) {
			// HTML color to RGB
			if (preg_match("/#?(..)(..)(..)/", $this->bocolor, $match)) {
				$r = hexdec($match[1]);
				$g = hexdec($match[2]);
				$b = hexdec($match[3]);
				$pdf->SetDrawColor($r, $g, $b);
			}
		}
		// Paint the text color or they might use inherited colors by the previous function
		if (preg_match("/#?(..)(..)(..)/", $this->tcolor, $match)) {
			$r = hexdec($match[1]);
			$g = hexdec($match[2]);
			$b = hexdec($match[3]);
			$pdf->SetTextColor($r, $g, $b);
		} else {
			$pdf->SetTextColor(0, 0, 0);
		}

		// If current position (left)
		if ($this->left == ".") {
			$cX = $pdf->GetX();
		}
		// For static position add margin (also updates X)
		else $cX = $pdf->addMarginX($this->left);

		// Check the width if set to page wide OR set by xml to larger then page wide
		if (($this->width == 0) or ($this->width > $pdf->getRemainingWidthPDF())) {
			$this->width = $pdf->getRemainingWidthPDF();
		}
		// For current position
		if ($this->top == ".") {
			$this->top = $pdf->GetY();
		} else {
			$pdf->SetY($this->top);
		}

		// Check the last cell height and adjust the current cell height if needed
		if ($pdf->lastCellHeight > $this->height) {
			$this->height = $pdf->lastCellHeight;
		}
		// Check for pagebreak
		if (!empty($temptext)) {
			$cHT = $pdf->getNumLines($temptext, $this->width);
			$cHT = $cHT * $pdf->getCellHeightRatio() * $pdf->getCurrentStyleHeight();
			$cM = $pdf->getMargins();
			// Add padding
			$cHT += ($cM["cell"] * 2);
			// Add a new page if needed
			if ($pdf->checkPageBreakPDF($cHT)) {
				$this->top = $pdf->GetY();
			}
			$temptext = spanLTRRTL($temptext, "BOTH");
		}
		// HTML ready - last value is true
		$pdf->MultiCell($this->width, $this->height, $temptext, $this->border, $this->align, $this->fill, $this->newline, $cX, $this->top, $this->reseth, $this->stretch, true);
		// Reset the last cell height for the next line
		if ($this->newline >= 1) {
			$pdf->lastCellHeight = 0;
		}
		// OR save the last height if heigher then before
		elseif ($pdf->lastCellHeight < $pdf->getLastH()) {
			$pdf->lastCellHeight = $pdf->getLastH();
		}

		// Set up the url link if exists ontop of the cell
		if (!empty($this->url)) {
			$pdf->Link($cX, $this->top, $this->width, $this->height, $this->url);
		}
		// Reset the border and the text color to black or they will be inherited
		$pdf->SetDrawColor(0, 0, 0);
		$pdf->SetTextColor(0, 0, 0);
	}
}

/**
* HTML element - PDF Report
*
* @package webtrees
* @subpackage Reports
* @todo add info
*/
class HtmlPDF extends Html {

	function HtmlPDF($tag, $attrs) {
		parent::Html($tag, $attrs);
	}

	function render(&$pdf, $sub = false) {
		if (!empty($this->attrs["pgvrstyle"])) {
			$pdf->setCurrentStyle($this->attrs["pgvrstyle"]);
		}
		if (!empty($this->attrs["width"])) {
			$this->attrs["width"] *= 3.9;
		}

		$this->text = $this->getStart().$this->text;
		foreach ($this->elements as $element) {
			if (is_string($element) && $element=="footnotetexts") {
				$pdf->Footnotes();
			} elseif (is_string($element) && $element=="addpage") {
				$pdf->newPage();
			} elseif ($element->get_type()=="Html") {
				$this->text .= $element->render($pdf, true);
			} else {
				$element->render($pdf);
			}
		}
		$this->text .= $this->getEnd();
		if ($sub) {
			return $this->text;
		}
		$pdf->writeHTML($this->text); //prints 2 empty cells in the Expanded Relatives report
		return 0;
	}
}

/**
* TextBox element
*
* @package webtrees
* @subpackage Reports
* @todo add info
*/
class TextBoxPDF extends TextBox {
	/**
	* Create a class Text Box for PDF
	*
	* @param float $width Text box width
	* @param float $height Text box height
	* @param boolean $border
	* @param string $bgcolor Background color code in HTML
	* @param boolean $newline
	* @param mixed $left
	* @param mixed $top
	* @param boolean $pagecheck
	* @param string $style
	* @param boolean $fill
	* @param boolean $padding
	* @param boolean $reseth Reset the last height after this bos is done
	*/
	function TextBoxPDF($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth) {
		parent::TextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth);
	}

	/**
	* PDF Text Box renderer
	* @param PDF &$pdf
	*/
	function render(&$pdf) {

		$newelements = array();
		$lastelement = "";
		$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
					$pdf->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
		$cW = 0; // Class Width
		$cX = 0; // Class Left
		$cY = 0; // Class Top
		// Used with line breaks and cell height calculation within this box
		$pdf->largestFontHeight = 0;

		// If current position (left)
		if ($this->left == ".") {
			$cX = $pdf->GetX();
		}
		// For static position add margin (returns and updates X)
		else $cX = $pdf->addMarginX($this->left);

		// If current position (top)
		if ($this->top == ".") {
			$cY = $pdf->GetY();
		} else {
			$cY = $this->top;
			$pdf->SetY($cY);
		}

		// Check the width if set to page wide OR set by xml to larger then page width (margin)
		if (($this->width == 0) or ($this->width > $pdf->getRemainingWidthPDF())) {
			$cW = $pdf->getRemainingWidthPDF();
		} else {
			$cW = $this->width;
		}

		// Save the original margins
		$cM = $pdf->getMargins();
		// Use cell padding to wrap the width
		// Temp Width with cell padding
		$cWT = $cW - ($cM["cell"] * 2);
		// Element height (exept text)
		$eH = 0;
		$w = 0;
		// Temp Height
		$cHT = 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);
		//-- calculate the text box height + width
		for ($i = 0; $i < $cE; $i++) {
			if (is_object($this->elements[$i])) {
				$ew = $this->elements[$i]->setWrapWidth($cWT - $w, $cWT);
				if ($ew == $cWT) {
					$w = 0;
				}
				$lw = $this->elements[$i]->getWidth($pdf);
				// 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 > $cWT) {
					$w = $lw[0];
				}
				// Footnote is at the bottom of the page. No need to calculate it's height or wrap the text!
				// We are changing the margins anyway!
				// For anything else but text (images), get the height
				$eH += $this->elements[$i]->getHeight($pdf);
			}
			//else {
				//$h += $pdf->getFootnotesHeight();
			//}
		}

		// 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) {
				// This is text elements. Number of LF but at least one line
				$cHT = ($cHT + 1) * $pdf->getCellHeightRatio();
				// Calculate the cell hight with the largest font size used within this Box
				$cHT = $cHT * $pdf->largestFontHeight;
				// Add cell padding
				if ($this->padding) {
					$cHT += ($cM["cell"] * 2);
				}
				if ($cH < $cHT) {
					$cH = $cHT;
				}
			}
			// This is any other element
			elseif ($cH < $eH) {
				$cH = $eH;
			}
		}
		// Finaly, check the last cells height
		if ($cH < $pdf->lastCellHeight) {
			$cH = $pdf->lastCellHeight;
		}
		// Add a new page if needed
		if ($this->pagecheck) {
			// Reset last cell height or Header/Footer will inherit it, in case of pagebreak
			$pdf->lastCellHeight = 0;
			if ($pdf->checkPageBreakPDF($cH)) {
				$cY = $pdf->GetY();
			}
		}

		// Setup the border and background color
		$cS = ""; // Class Style
		if ($this->border) $cS = "D"; // D or empty string: Draw (default)
		$match = array();
		// Fill the background
		if ($this->fill) {
			if (!empty($this->bgcolor)) {
				if (preg_match("/#?(..)(..)(..)/", $this->bgcolor, $match)) {
					$cS .= "F"; // F: Fill the background
					$r = hexdec($match[1]);
					$g = hexdec($match[2]);
					$b = hexdec($match[3]);
					$pdf->SetFillColor($r, $g, $b);
				}
			}
		}
		// Clean up a bit
		unset($lw, $w, $match, $cE, $eH);
		// Draw the border
		if (!empty($cS)) {
			$pdf->Rect($cX, $cY, $cW, $cH, $cS);
		}
		// Add cell pedding if set and if any text (element) exist
		if ($this->padding) {
			if ($cHT > 0) {
				$pdf->SetY($cY + $cM["cell"]);
			}
		}
		// Change the margins X, Width
		if (!$pdf->getRTL()) {
			if ($this->padding) {
				$pdf->SetLeftMargin($cX + $cM["cell"]);
				$pdf->SetRightMargin($pdf->getRemainingWidthPDF() - $cW + $cM["right"]);
			}
			else {
				$pdf->SetLeftMargin($cX);
				$pdf->SetRightMargin($pdf->getRemainingWidthPDF() - $cW + $cM["right"]);
			}
		} else {
			if ($this->padding) {
				$pdf->SetRightMargin($cX + $cM["cell"]);
				$pdf->SetLeftMargin($pdf->getRemainingWidthPDF() - $cW  + $cM["left"]);
			} else {
				$pdf->SetRightMargin($cX);
				$pdf->SetLeftMargin($pdf->getRemainingWidthPDF() - $cW  + $cM["left"]);
			}
		}
		// Save the current page number
		$cPN = $pdf->getPage();

		// Render the elements (write text, print picture...)
		foreach ($this->elements as $element) {
			if (is_object($element)) {
				$element->render($pdf);
			} elseif (is_string($element) and $element == "footnotetexts") {
				$pdf->Footnotes();
			} elseif (is_string($element) and $element == "addpage") {
				$pdf->newPage();
			}
		}
		// Restore the margins
		$pdf->SetLeftMargin($cM["left"]);
		$pdf->SetRightMargin($cM["right"]);

		// This will be mostly used to trick the multiple images last height
		if ($this->reseth) {
			$cH = 0;
			// This can only happen with multiple images and with pagebreak
			if ($cPN != $pdf->getPage()) {
				$pdf->setPage($cPN);
			}
		}
		// New line and some clean up
		if (!$this->newline) {
			$pdf->SetXY(($cX + $cW), $cY);
			$pdf->lastCellHeight = $cH;
		} else {
			// addMarginX() also updates X
			$pdf->addMarginX(0);
			$pdf->SetY($cY + $cH);
			$pdf->lastCellHeight = 0;
		}
		return true;
	}
}

/**
* Text element
*
* @package webtrees
* @subpackage Reports
* @todo add info
*/
class TextPDF extends Text {
	/**
	* Create a Text class for PDF
	*
	* @param string $style The name of the text style
	* @param string $color HTML color code
	*/
	function TextPDF($style, $color) {
		parent::Text($style, $color);
	}

	/**
	* PDF Text renderer
	* @param PDF &$pdf
	*/
	function render(&$pdf) {
		// Set up the style
		if ($pdf->getCurrentStyle() != $this->styleName) {
			$pdf->setCurrentStyle($this->styleName);
		}
		$temptext = str_replace("#PAGENUM#", $pdf->PageNo(), $this->text);

		// Paint the text color or they might use inherited colors by the previous function
		$match = array();
		if (preg_match("/#?(..)(..)(..)/", $this->color, $match)) {
			$r = hexdec($match[1]);
			$g = hexdec($match[2]);
			$b = hexdec($match[3]);
			$pdf->SetTextColor($r, $g, $b);
		} else {
			$pdf->SetTextColor(0, 0, 0);
		}
		$temptext = spanLTRRTL($temptext, "BOTH");
		$pdf->writeHTML($temptext, false, false, true, false, ""); //change height - line break etc.
		// Reset the text color to black or it will be inherited
		$pdf->SetTextColor(0, 0, 0);
	}

	/**
	* Returns the height in points of the text element
	*
	* The height is already calculated in getWidth()
	* @param PDF &$pdf
	* @return float 0
	*/
	function getHeight(&$pdf) {
		return 0;
	}

	/**
	* Splits the text into lines if necessary to fit into a giving cell
	*
	* @param PDF &$pdf
	* @return array
	*/
	function getWidth(&$pdf) {
		// Setup the style name, a font must be selected to calculate the width
		if ($pdf->getCurrentStyle() != $this->styleName) {
			$pdf->setCurrentStyle($this->styleName);
		}
		// Check for the largest font size in the box
		$fsize = $pdf->getCurrentStyleHeight();
		if ($fsize > $pdf->largestFontHeight) {
			$pdf->largestFontHeight = $fsize;
		}

		// Get the line width
		$lw = $pdf->GetStringWidth($this->text);
		// Line Feed counter - Number of lines in the text
		$lfct = substr_count($this->text, "\n") + 1;
		// If there is still remaining wrap width...
		if ($this->wrapWidthRemaining > 0) {
			// Check with line counter too!
			// but floor the $wrapWidthRemaining first to keep it bugfree!
			$wrapWidthRemaining = floor($this->wrapWidthRemaining);
			if (($lw >= ($wrapWidthRemaining)) or ($lfct > 1)) {
				$newtext = "";
				$lines = explode("\n", $this->text);
				// Go throught the text line by line
				foreach ($lines as $line) {
					// Line width in points + a little margin
					$lw = $pdf->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 += $pdf->GetStringWidth($word." ");
							if ($lw <= $wrapWidthRemaining) {
								$newtext .= $word;
								if ($addspace != 0) {
									$newtext .= " ";
								}
							} else {
								$lw = $pdf->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 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
*
* @package webtrees
* @subpackage Reports
* @todo add info
*/
class FootnotePDF extends Footnote {

	function FootnotePDF($style="") {
		parent::Footnote($style);
	}

	/**
	* PDF Footnotes number renderer
	* @param PDF &$pdf
	*/
	function render(&$pdf) {
		$pdf->setCurrentStyle("footnotenum");
		$pdf->Write($pdf->getCurrentStyleHeight(), $this->numText, $this->addlink); //source link numbers after name
	}

	/**
	* Write the Footnote text
	* Uses style name "footnote" by default
	*
	* @param PDF &$pdf
	*/
	function renderFootnote(&$pdf) {
		if ($pdf->getCurrentStyle() != $this->styleName) {
			$pdf->setCurrentStyle($this->styleName);
		}
		$temptext = str_replace("#PAGENUM#", $pdf->PageNo(), $this->text);
		// Set the link to this y/page position
		$pdf->SetLink($this->addlink, -1, -1);
		// Print first the source number
		// working
		if ($pdf->getRTL()) {
			$pdf->writeHTML("<span> .".$this->num."</span>", false, false, false, false, "");
		} else {
			$temptext = "<span>".$this->num.". </span>".$temptext;
		}
		$temptext = spanLTRRTL($temptext, "BOTH");
		$pdf->writeHTML($temptext."<br/>", true, false, true, false, "");
		//$pdf->Write($pdf->getCurrentStyleHeight(), $this->num.". ".$temptext."\n\n"); //@@ indi list of sources
	}

	/**
	* Returns the height in points of the Footnote element
	*
	* @param PDF &$pdf
	* @return float $h
	*/
	function getFootnoteHeight(&$pdf) {
		//$style = $pdf->getStyle($this->styleName);
		//$ct = substr_count($this->numText, "\n");
		//if ($ct > 0) {
			//$ct += 1;
		//}
		//$h = ($style["size"] * $ct);
		//return $h;
		return 0;
	}

	/**
	* Splits the text into lines to fit into a giving cell
	* and returns the last lines width
	*
	* @param PDF &$pdf
	* @return array
	*/
	function getWidth(&$pdf) {
		// Setup the style name, a font must be selected to calculate the width
		$pdf->setCurrentStyle("footnotenum");

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

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

		// Get the line width
		$lw = ceil($pdf->GetStringWidth($this->numText));
		// Line Feed counter - Number of lines in the text
		$lfct = substr_count($this->numText, "\n") + 1;
		// If there is still remaining wrap width...
		if ($this->wrapWidthRemaining > 0) {
			// Check with line counter too!
			// but floor the $wrapWidthRemaining first to keep it bugfree!
			$wrapWidthRemaining = floor($this->wrapWidthRemaining);
			if (($lw >= $wrapWidthRemaining) or ($lfct > 1)) {
				$newtext = "";
				$lines = explode("\n", $this->numText);
				// Go throught the text line by line
				foreach ($lines as $line) {
					// Line width in points
					$lw = ceil($pdf->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 += ceil($pdf->GetStringWidth($word." "));
							if ($lw < $wrapWidthRemaining) {
								$newtext .= $word;
								if ($addspace != 0) {
									$newtext .= " ";
								}
							} else {
								$lw = $pdf->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
*
* @package webtrees
* @subpackage Reports
*/
class PageHeaderPDF extends PageHeader {

	function PageHeaderPDF() {
		parent::PageHeader();
	}

	/**
	* PageHeader element renderer
	* @param PDF &$pdf
	*/
	function render(&$pdf) {
		$pdf->clearPageHeader();
		foreach ($this->elements as $element) {
			$pdf->addPageHeader($element);
		}
	}
}

/**
* ImagePDF class element
*
* @package webtrees
* @subpackage Reports
*/
class ImagePDF extends Image {

	function ImagePDF($file, $x, $y, $w, $h, $align, $ln) {
		parent::Image($file, $x, $y, $w, $h, $align, $ln);
	}

	/**
	* PDF image renderer
	* @param PDF &$pdf
	*/
	function render(&$pdf) {
		global $lastpicbottom, $lastpicpage, $lastpicleft, $lastpicright;

		// Check for a pagebreak first
		if ($pdf->checkPageBreakPDF($this->height + 5)) {
			$this->y = $pdf->GetY();
		}

		$curx = $pdf->GetX();
		// If current position (left)set "."
		if ($this->x == ".") {
			$this->x = $pdf->GetX();
		}
		// For static position add margin
		else  {
			$this->x = $pdf->addMarginX($this->x);
			$pdf->SetX($curx);
		}
		if ($this->y == ".") {
			//-- first check for a collision with the last picture
			if (isset($lastpicbottom)) {
				if (($pdf->PageNo()==$lastpicpage) && ($lastpicbottom >= $pdf->GetY()) && ($this->x >= $lastpicleft) && ($this->x <= $lastpicright))
					$pdf->SetY($lastpicbottom + 5);
			}
			$this->y = $pdf->GetY();
		} else {
			$pdf->SetY($this->y);
		}

		$pdf->Image($this->file, $this->x, $this->y, $this->width, $this->height, "", "", $this->line, false, 72, $this->align);
		$lastpicpage = $pdf->PageNo();
		$pdf->lastpicpage = $pdf->getPage();
		$lastpicleft = $this->x;
		$lastpicright = $this->x + $this->width;
		$lastpicbottom = $this->y + $this->height;
		// Setup for the next line
		if ($this->line == "N") {
			$pdf->SetY($lastpicbottom);
		}
	}

	/**
	* Get the image height
	* @param PDF &$pdf
	* @return float
	*/
	function getHeight(&$pdf) {
		return $this->height;
	}

	function getWidth(&$pdf) {
		return $this->width;
	}
}

/**
* Line element
*
* @package webtrees
* @subpackage Reports
* @todo add info
*/
class LinePDF extends Line {
	/**
	* Create a line class -PDF
	* @param mixed $x1
	* @param mixed $y1
	* @param mixed $x2
	* @param mixed $y2
	*/
	function LinePDF($x1, $y1, $x2, $y2) {
		parent::Line($x1, $y1, $x2, $y2);
	}

	/**
	* PDF line renderer
	* @param PDF &$pdf
	*/
	function render(&$pdf) {
		if ($this->x1 == ".") $this->x1=$pdf->GetX();
		if ($this->y1 == ".") $this->y1=$pdf->GetY();
		if ($this->x2 == ".") {
			$this->x2 = $pdf->getMaxLineWidth();
		}
		if ($this->y2 == ".") $this->y2=$pdf->GetY();

		$pdf->Line($this->x1, $this->y1, $this->x2, $this->y2);
	}
}
