<?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 DISK_CACHE = 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::UNITS, array(
			$this->pagew,
			$this->pageh
		), self::UNICODE, "UTF-8", self::DISK_CACHE);

		// 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 WT_Report_PDF_Cell(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 integer
	 */
	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 integer $width   cell width (expressed in points)
	 * @param integer $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 integer $ln      Indicates where the current position should go after the call
	 * @param mixed   $top     Y-position
	 * @param mixed   $left    X-position
	 * @param integer $fill    Indicates if the cell background must be painted (1) or transparent (0). Default value: 1
	 * @param integer $stretch Stretch carachter mode
	 * @param string  $bocolor Border color
	 * @param string  $tcolor  Text color
	 * @param boolean $reseth
	 *
	 * @return WT_Report_PDF_Cell
	 */
	function createCell(
		$width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth
	) {
		return new WT_Report_PDF_Cell($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 WT_Report_PDF_TextBox
	 */
	function createTextBox(
		$width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth
	) {
		return new WT_Report_PDF_TextBox($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 WT_Report_PDF_Text
	 */
	function createText($style, $color) {
		return new WT_Report_PDF_Text($style, $color);
	}

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

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

	/**
	 * Create a new image object - WT_Report_PDF
	 *
	 * @param string  $file  Filename
	 * @param mixed   $x
	 * @param mixed   $y
	 * @param integer $w     Image width
	 * @param integer $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 WT_Report_PDF_Image
	 */
	function createImage($file, $x, $y, $w, $h, $align, $ln) {
		return new WT_Report_PDF_Image($file, $x, $y, $w, $h, $align, $ln);
	}

	/**
	 * Create a new image object from WT_Media Object - WT_Report_PDF
	 *
	 * @param WT_Media $mediaobject
	 * @param mixed    $x
	 * @param mixed    $y
	 * @param integer  $w           Image width
	 * @param integer  $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 WT_Report_PDF_Image
	 */
	function createImageFromObject($mediaobject, $x, $y, $w, $h, $align, $ln) {
		return new WT_Report_PDF_Image($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 WT_Report_PDF_Line
	 */
	function createLine($x1, $y1, $x2, $y2) {
		return new WT_Report_PDF_Line($x1, $y1, $x2, $y2);
	}

	/**
	 * @param $tag
	 * @param $attrs
	 *
	 * @return WT_Report_PDF_Html
	 */
	function createHTML($tag, $attrs) {
		return new WT_Report_PDF_Html($tag, $attrs);
	}
}

/**
 * WT Report PDF Class
 *
 * This class inherits from the TCPDF class and is used to generate the PDF document
 */
class PDF extends TCPDF {
	/** @var WT_Report_Base_Element[] Array of elements in the header */
	public $headerElements = array();

	/** @var WT_Report_Base_Element[] Array of elements in the page header */
	public $pageHeaderElements = array();

	/** @var WT_Report_Base_Element[] Array of elements in the footer */
	public $footerElements = array();

	/** @var WT_Report_Base_Element[] Array of elements in the body */
	public $bodyElements = array();

	/** @var WT_Report_Base_Footnote[] Array of elements in the footer notes */
	public $printedfootnotes = array();

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

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

	/** @var int The largest font size within a TextBox to calculate the height */
	public $largestFontHeight = 0;

	/** @var int The last pictures page number */
	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 integer 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 integer 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 integer 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 integer The number of the Footer elements
	 */
	function addFooter($element) {
		$this->footerElements[] = $element;

		return count($this->footerElements) - 1;
	}

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

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

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

	/**
	 * @param $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();
	}

	/**
	 * @param $r
	 */
	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']);
		}
	}

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

		return $h;
	}

	/**
	 * Returns the the current font size height -PDF
	 *
	 * @return integer
	 */
	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 integer $height Cell height
	 *
	 * @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();
	}
}
