<?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) 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
use WT\Auth;

/**
 * Main WT Report Class for PDF
 */
class WT_Report_PDF extends WT_Report_Base {
	/**
	 * 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;
	/**
	 * FALSE means that the full font is embedded, TRUE means only the used chars
	 * in TCPDF v5.9 font subsetting is a very slow process, this leads to larger files
	 *
	 * @var boolean const
	 */
	const subsetting = false;
	/**
	 * A new object of the PDF class
	 *
	 * @var PDF
	 */
	public $pdf;

	/**
	 * PDF Setup - WT_Report_PDF
	 */
	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, "UTF-8", 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);
		// Set font subsetting
		$this->pdf->setFontSubsetting(self::subsetting);
		// 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 (Auth::isAdmin()) {
			$appversion .= " ".WT_VERSION;
		}
		$this->pdf->SetCreator($appversion . " (" . parent::wt_url . ")");
		// Not implemented yet - WT_Report_Base::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::wt_url);
			$this->pdf->addFooter($element);
		}
	}

	/**
	 * Add an element - WT_Report_PDF
	 *
	 * @param object|string $element Object or string
	 *
	 * @return int
	 */
	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-' . uniqid() . '.pdf', 'I');
	}

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

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

	/**
	 * Create a new Cell object - WT_Report_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
	 *
	 * @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 - WT_Report_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
	 *
	 * @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- WT_Report_PDF
	 *
	 * @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 - WT_Report_PDF
	 *
	 * @param string $style Style name
	 *
	 * @return FootnotePDF
	 */
	function createFootnote($style) {
		return new FootnotePDF($style);
	}

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

	/**
	 * Create a new image object - WT_Report_PDF
	 *
	 * @param string $file  Filename
	 * @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 image object from WT_Media Object - WT_Report_PDF
	 *
	 * @param string $mediaobject WT_Media Object
	 * @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 createImageFromObject($mediaobject, $x, $y, $w, $h, $align, $ln) {
		return new ImagePDF($mediaobject->getServerFilename('thumb'), $x, $y, $w, $h, $align, $ln);
	}

	/**
	 * Create a new line object - WT_Report_PDF
	 *
	 * @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);
	}

	/**
	 * @param $tag
	 * @param $attrs
	 *
	 * @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
 */
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->wt_report = $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->wt_report->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->wt_report->Styles[$s])) {
			$s                           = $this->getCurrentStyle();
			$this->wt_report->Styles[$s] = $s;
		}

		return $this->wt_report->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->wt_report->defaultFontSize;
		}
		$style = $this->wt_report->getStyle($this->currentStyle);

		return $style['size'];
	}

	/**
	 * Checks the Footnote and numbers them
	 *
	 * @param object $footnote
	 *
	 * @return boolean 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

/**
 * Cell element - PDF
 */
class CellPDF extends Cell {
	/**
	 * PDF Cell renderer
	 *
	 * @param PDF $pdf
	 *
	 * @return void
	 */
	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);
		// underline «title» part of Source item
		$temptext = str_replace(array('«', '»'), array('<u>', '</u>'), $temptext);
		$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 || $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
			if (is_array($cM['cell'])) {
				$cHT += ($cM['padding_bottom'] + $cM['padding_top']);
			} else {
				$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
 */
class HtmlPDF extends Html {
	function render($pdf, $sub = false) {
		if (!empty($this->attrs['style'])) {
			$pdf->setCurrentStyle($this->attrs['style']);
		}
		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
 */
class TextBoxPDF extends TextBox {
	/**
	 * PDF Text Box renderer
	 *
	 * @param PDF $pdf
	 *
	 * @return bool|int
	 */
	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 || $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
		if (is_array($cM['cell'])) {
			$cWT = $cW - ($cM['padding_left'] + $cM['padding_right']);
		} else {
			$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) {
					if (is_array($cM['cell'])) {
						$cHT += ($cM['padding_bottom'] + $cM['padding_top']);
					} else {
						$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)) {
			if (!$pdf->getRTL()) {
				$cXM = $cX;
			} else {
				$cXM = ($pdf->getPageWidth()) - $cX - $cW;
			}
			//echo "<br>cX=".$cX."  cXM=".$cXM."  cW=".$cW."  LW=".$pdf->getPageWidth()."  RW=".$pdf->getRemainingWidthPDF()."  MLW=".$pdf->getMaxLineWidth();
			$pdf->Rect($cXM, $cY, $cW, $cH, $cS);
		}
		// Add cell padding if set and if any text (element) exist
		if ($this->padding) {
			if ($cHT > 0) {
				if (is_array($cM['cell'])) {
					$pdf->SetY($cY + $cM['padding_top']);
				} else {
					$pdf->SetY($cY + $cM['cell']);
				}
			}
		}
		// Change the margins X, Width
		if (!$pdf->getRTL()) {
			if ($this->padding) {
				if (is_array($cM['cell'])) {
					$pdf->SetLeftMargin($cX + $cM['padding_left']);
				} else {
					$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) {
				if (is_array($cM['cell'])) {
					$pdf->SetRightMargin($cX + $cM['padding_right']);
				} else {
					$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) && $element == 'footnotetexts') {
				$pdf->Footnotes();
			} elseif (is_string($element) && $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
 */
class TextPDF extends Text {
	/**
	 * PDF Text renderer
	 *
	 * @param PDF $pdf
	 *
	 * @return void
	 */
	function render($pdf) {
		// Set up the style
		if ($pdf->getCurrentStyle() != $this->styleName) {
			$pdf->setCurrentStyle($this->styleName);
		}
		$temptext = str_replace("#PAGENUM#", $pdf->PageNo(), $this->text);
		// underline «title» part of Source item
		$temptext = str_replace(array('«', '»'), array('<u>', '</u>'), $temptext);

		// 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");
		$temptext = str_replace(
			array('<br><span dir="rtl" >', '<br><span dir="ltr" >', '> ', ' <'),
			array('<span dir="rtl" ><br>', '<span dir="ltr" ><br>', '>&nbsp;', '&nbsp;<'),
			$temptext
		);
		$pdf->writeHTML(
			$temptext,
			false,
			false,
			true,
			false,
			""
		); //change height - line break etc. - the form is mirror on rtl pages
		// 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 = (int) ($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
 */
class FootnotePDF extends Footnote {
	/**
	 * PDF Footnotes number renderer
	 *
	 * @param PDF $pdf
	 *
	 * @return void
	 */
	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
	 *
	 * @return void
	 */
	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;
		}
		// underline «title» part of Source item
		$temptext = str_replace(array('«', '»'), array('<u>', '</u>'), $temptext);
		$pdf->writeHTML($temptext, true, false, true, false, '');
	}

	/**
	 * 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 = (int) ($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
 */
class PageHeaderPDF extends PageHeader {
	/**
	 * PageHeader element renderer
	 *
	 * @param PDF $pdf
	 *
	 * @return void
	 */
	function render($pdf) {
		$pdf->clearPageHeader();
		foreach ($this->elements as $element) {
			$pdf->addPageHeader($element);
		}
	}
}

/**
 * ImagePDF class element
 */
class ImagePDF extends Image {
	/**
	 * PDF image renderer
	 *
	 * @param PDF $pdf
	 *
	 * @return void
	 */
	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);
		}
		if ($pdf->getRTL()) {
			$pdf->Image(
				$this->file,
				$pdf->getPageWidth() - $this->x,
				$this->y,
				$this->width,
				$this->height,
				"",
				"",
				$this->line,
				false,
				72,
				$this->align
			);
		} else {
			$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
 */
class LinePDF extends Line {
	/**
	 * PDF line renderer
	 *
	 * @param PDF $pdf
	 *
	 * @return void
	 */
	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();
		}
		if ($pdf->getRTL()) {
			$pdf->Line($pdf->getPageWidth() - $this->x1, $this->y1, $pdf->getPageWidth() - $this->x2, $this->y2);
		} else {
			$pdf->Line($this->x1, $this->y1, $this->x2, $this->y2);
		}
		//@@ pedigree report lines - family, deaths, cemeteries???
	}
}
