<?php
/**
* @version	$Id: pdf_helper.php 14244 2011-03-16 20:53:41Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

defined('FULL_PATH') or die('restricted access!');

//define('PDF_DEBUG_NO_TEXT', 1); // do not draw text
//define('PDF_DEBUG_DUMP_ONLY', 1); // output elements dump and exit
//define('PDF_DEBUG_DRAW_BOXES', 1); //
//define('PDF_DEBUG_DRAW_BASELINE', 1); //
//define('PDF_DEBUG_DRAW_BOX_INFO', '/.*/'); //
//define('PDF_DEBUG_DRAW_BOX_INFO', '/(td|img)/i'); //
//define('PDF_DEBUG_DRAW_MARGINS', 1); //
//define('PDF_DEBUG_DRAW_ELEM_BORDERS', 1);
//define('PDF_DEBUG_DRAW_LINE_BOXES', 1);
//define('PDF_DRAW_CONTENT_AREA', 1);

require_once FULL_PATH.'/core/units/pdf/pdf_styles.php';
require_once FULL_PATH.'/core/units/pdf/pdf_text.php';
require_once FULL_PATH.'/core/units/pdf/pdf_table.php';
require_once FULL_PATH.'/core/units/pdf/pdf_image.php';
require_once FULL_PATH.'/core/units/pdf/pdf_renderer.php';
require_once FULL_PATH.'/core/units/pdf/pdf_renderer_tcpdf.php';

class kPDFHelper extends kHelper {

	/**
	 * Enter description here...
	 *
	 * @var kPDFRenderer
	 */
	var $PDF;
	var $CurPDFElem;

	var $DimensionsMode = 1;

	var $Stylesheet;

	var $PtPerEm = 10;

	public $Document = null;

	const DM_NORMAL = 0;
	const DM_SKIP = 1;

	/**
	 * Allows to convert given template contents into pdf document
	 *
	 * @param string $template (template name OR it's contents, see $raw_template)
	 * @param Array $template_params
	 * @param bool $is_content
	 * @return string
	 */
	function BuildFromTemplate($template, $template_params = Array (), $is_content = false)
	{
		$this->Application->InitParser();

		$this->Application->Parser->SetParams($template_params);

		if ($is_content) {
			$xml = $this->Application->Parser->Parse($template);
		}
		else {
			$xml = $this->Application->Parser->Run($template);
		}

		$xml_helper =& $this->Application->recallObject('kXMLHelper');
		/* @var $xml_helper kXMLHelper */

		$doc = $xml_helper->Parse($xml_helper->ConvertHTMLEntities($xml), kXMLHelper::XML_WITH_TEXT_NODES);
		if ($doc->Name == 'ERROR') {
			echo 'BAD TEMPLATE';
			exit;
		}

//		$this->Application->Debugger->profileStart('pdf_init', 'Initializing PDF');
		$this->InitPDF($doc);
//		$this->Application->Debugger->profileFinish('pdf_init');
//		$this->Application->Debugger->profileStart('process_nodes', 'Processing nodes');
		$this->ProcessNode($doc);
//		$this->Application->Debugger->profileFinish('process_nodes');

		if (defined('PDF_DEBUG_DUMP_ONLY')) {
			$this->DimensionsMode = kPDFHelper::DM_NORMAL ;
			$this->Application->Debugger->profileStart('laying_out', 'Laying out nodes');
//			$this->CurPDFElem->LayoutChildren();
			$this->Application->Debugger->profileFinish('laying_out');
			$this->Application->Debugger->profileStart('rendering', 'rendering');
//			$this->Render();
			$this->Application->Debugger->profileFinish('rendering');

//			echo $this->CurPDFElem->DumpStructure();
			exit;
		}

		$this->Render();

		return $this->PDF->GetPDFString();
//		exit;
	}

	function InitPDF($doc)
	{
		$this->PDF = new kTCPDFRenderer();

		$this->Stylesheet = new kPDFStylesheet();

		$this->CurPDFElem = kPDFElemFactory::CreateFromNode($doc, $this);
		$this->Document = $this->CurPDFElem;
//		$this->CurPDFElem = new kPDFPage($page1, new kXMLNode('_NONE_', array('style' => 'display: block; width: '.$page1->getWidth().';')), $this);
		$this->CurPDFElem->Init();
		$this->CurPDFElem->SetCSSProperty('width', $this->PDF->getWidth());
		$this->CurPDFElem->SetCSSProperty('height', $this->PDF->getHeight());
		$this->PDF->SetFont('helvetica', $this->PtPerEm);
	}

	function Render()
	{
		$this->DimensionsMode = kPDFHelper::DM_NORMAL ;
		$this->CurPDFElem->LayoutChildren();
		$this->PageBottom = $this->PDF->getHeight() - $this->Document->GetCSSProperty('margin-bottom'); // margin
		$this->CurPDFElem->DrawAt($this->PDF, 0, 0);
	}

	function ProcessHead($node)
	{
		if ($node->Name == 'STYLE') {
			$css = $node->Data;
			$tokens = $this->Stylesheet->GetTokens($node->Data);
			$this->Stylesheet->ParseTokens($tokens);
		}
		foreach($node->Children as $child) {
			$this->ProcessHead($child);
		}
	}

	function ProcessNode($node)
	{
		$children = count($node->Children);
		foreach($node->Children as $child) {
			if ($child->Name == 'HEAD') {
				$this->ProcessHead($child);
				continue;
			}

			$elem = kPDFElemFactory::CreateFromNode($child, $this);
			if (!$elem)  continue;
			$display = $elem->GetDisplayLevel();
			if ($display == 'none')  continue;

			// http://manual.prod.intechnic.lv/css21/visuren.html#anonymous-block-level
			if ($display == 'block' && $this->CurPDFElem->GetDisplayLevel() == 'inline') {
				$this->CurPDFElem->Closed();
				$this->CurPDFElem = $this->CurPDFElem->GetContainingBlock();
			}

			$current = $this->CurPDFElem;
			$line = false;
			if ($display == 'inline' && !($this->CurPDFElem instanceof kPDFLine) ) {
				$this->CurPDFElem = $this->CurPDFElem->GetLineBox();
				$line = $this->CurPDFElem;
			}

				$this->CurPDFElem = $this->CurPDFElem->AddChild( $elem );

			$this->ProcessNode($child);
			$this->CurPDFElem = $current;
		}
		$this->CurPDFElem->Closed();
	}
}

class kPDFElemFactory {
	static function CreateFromNode($node, $helper)
	{
		/* @todo Create elements, based on their display, rather than tag name - pure CSS rendering */

		switch ($node->Name) {
			case '_TEXT_':
//				$DOMNode = $node->Parent;
				$elem = new kPDFTextElement($node->Data, $node, $helper);
				if ($elem->CSSSpecifiedProperties['WHITE-SPACE'] == 'normal' && trim($node->Data) == '') {
					return false;
				}
				return $elem;
				break;
			case 'TR':
				return new kPDFTableRow($node, $helper);
			case 'BR':
				return new kPDFTextElement($node->Data, $node, $helper);
			case 'TABLE':
				return new kPDFTable($node, $helper);
			case 'IMG':
				return new kPDFImage($node, $helper);
			default:
				return new kPDFElement($node, $helper);
				break;
		}
	}
}

class kPDFElement {
	public $Parent;
	protected $Font;
	protected $FontSize;
	public $Children = array();

	public $Position = 0;

	public $FirstChild;
	public $LastChild;

	public $ContentHeight = 0;

	public $Style;

	public $CurX = 0;
	public $CurY = 0;

	public $Baseline = 0;
	public $Ascent;
	public $Descent;
	public $Gap;

	public $Node;
	public $Helper;

	public $CSSSpecifiedProperties = array();
	public $CSSComputedPoperties = array();
	public $DirectStyle;

	public $IsClosed = false;

	public $LayedOut = false;

	public $WidthComputed = false;

	public $MinContentWidth = 0;
	public $MaxContentWidth = 0;

	public $CurLine_MinContentWidth = 0;
	public $CurLine_MaxContentWidth = 0;

	public $Spacers = 0;


	function __construct($node, $helper)
	{
		$this->Node = $node;
		$this->Helper = $helper;
		$this->ProcessCSS();
	}

	function Init()
	{
		$this->ComputeCSSProperties();
	}

	function Reset()
	{
		$this->CurX = 0;
		$this->CurY = 0;
	}

	function AddChild($child, $init=true, $after=null)
	{
		$node_count = count($this->Children);
		if (isset($after)) {
			$after_pos = $after->Position;
			for($i=$after_pos+1; $i<$node_count; $i++) {
				$this->Children[$i]->Position++;
			}
			array_splice($this->Children, $after_pos+1, 0, array($child));
			$child->Position = $after_pos+1;
		}
		else {
			$child->Position = $node_count;
			$this->Children[] = $child;
		}
		if ($node_count == 0) {
			$this->FirstChild = $child;
			$this->LastChild = $child;
		}
		elseif ($child->Position == $node_count) {
			$this->LastChild = $child;
		}

		$child->Parent = $this;
		if ($init) $child->Init();
		return $child;
	}

	public function Closed()
	{
		if ($this->LastChild && $this->LastChild instanceof kPDFLine && !$this->LastChild->IsClosed) {
			$this->LastChild->Closed();
		}
		$this->ComputeWidthAndMargins();
		$this->CalcMinMaxContentWidth();
		$this->IsClosed = true;
		return ;
	}

	function RemoveChildren($start, $count=null)
	{
		return array_splice($this->Children, $start);
	}

	function &FindChildByProperty($property, $value)
	{
		$property = strtoupper($property);
		if (strtoupper($this->GetCSSProperty($property)) == strtoupper($value)) return $this;
		foreach ($this->Children as $elem)
		{
			$child =& $elem->FindChildByProperty($property, $value);
			if ($child !== false)
			{
				return $child;
			}
		}
		$false = false;
		return $false;
	}

	function LayoutChildren()
	{
		$this->ComputeWidthAndMargins();
		$i = 0;
		$this->Reset();
		while (isset($this->Children[$i])) { // can't use foreach here, because LayoutChildren() may add new children to current elem (wrapping)
			$child = $this->Children[$i];
			$child->LayoutChildren();
			$i++;
		}
		$this->CheckDimensions();
		$this->ComputeHeight();
//		$this->ComputeBaseline();
	}

	public function ComputeHeight()
	{
		$display = $this->GetCSSProperty('display');
		if  ($display == 'inline') return ;

		$height = 0; // $this->GetCSSProperty('height');
	/*	$extra_height =
					$this->GetCSSProperty('margin-top') +
					$this->GetCSSProperty('margin-bottom') +
					$this->GetCSSProperty('padding-top') +
					$this->GetCSSProperty('padding-bottom') +
					$this->GetCSSProperty('border-top-width') +
					$this->GetCSSProperty('border-bottom-width');*/

		foreach ($this->Children as $elem) {
			$dim = $elem->GetBoxDimensions();
			$elem_height = $dim[1];
			if ($elem->GetDisplayLevel() == 'block' && $elem->GetCSSProperty('display') != 'table-cell') {
				$height += $elem_height;
			}
			else {
				if ($elem_height > $height) {
					$height = $elem_height;
				}
			}
		}
		$this->SetCSSProperty('height', $height);
	}

	function &FindChild($name)
	{
		$name = strtoupper($name);
		if ($this->Node->Name == $name) return $this;
		foreach ($this->Children as $elem)
		{
			$child =& $elem->FindChild($name);
			if ($child !== false)
			{
				return $child;
			}
		}
		$false = false;
		return $false;
	}

	function DumpStructure($level=0)
	{
		$dump = '';
		$tab = str_repeat('&nbsp;', $level*4);
		$dump .= $tab . get_class($this) . ' Node: ' . $this->Node->Name . " child of ".$this->Node->Parent->Name."<br>\n";
		if ($this instanceof kPDFTextElement) {
			$dump .= $tab . 'Data: ' . $this->Data . "<br>\n";
		}
		$dump .= $tab . 'Width: ' . $this->GetCSSProperty('width') . ' Height: ' . $this->GetCSSProperty('height') . " Ascent: " . $this->Ascent . "<br>\n";
		$dump .= $tab . 'MinCW: ' . $this->MinContentWidth . ' MaxCW: ' . $this->MaxContentWidth . "<br>\n";
		$dump .= $tab . 'Children:' . "<br>\n";
		$i = 0;
		foreach ($this->Children as $elem)
		{
			$i++;
			$dump .= $tab . $i . "<br>\n";
			$level++;
			$dump .= $elem->DumpStructure($level);
			$level--;
		}
		return $dump;
	}

	function DrawAt($page, $x=0, $y=0, $spacer_w=0)
	{
		if ($this->Node->Name == 'FOOTER') {
			$dim = $this->GetBoxDimensions();
			$y = $this->Helper->PDF->GetHeight() - $dim[1];
		}

		if ($this->GetDisplayLevel() == 'block' ) {
			$width = $this->GetCSSProperty('width');
			$margin_left = $this->GetCSSProperty('margin-left');
			$margin_right = $this->GetCSSProperty('margin-right');
			$border_left = $this->GetCSSProperty('border-left-width');
			$border_right = $this->GetCSSProperty('border-right-width');
			$padding_left = $this->GetCSSProperty('padding-left');
			$padding_right = $this->GetCSSProperty('padding-right');

			$height = $this->GetCSSProperty('height');
			$margin_top = $this->GetCSSProperty('margin-top');
			$margin_bottom = $this->GetCSSProperty('margin-bottom');
			$border_top = $this->GetCSSProperty('border-top-width');
			$border_bottom = $this->GetCSSProperty('border-bottom-width');
			$padding_top = $this->GetCSSProperty('padding-top');
			$padding_bottom = $this->GetCSSProperty('padding-bottom');

			$outer_width = $margin_left + $border_left + $padding_left + $width + $padding_right + $border_right + $margin_right;
			$outer_height = $margin_top + $border_top + $padding_top + $height + $padding_bottom + $border_bottom + $margin_bottom;

			if (defined('PDF_DEBUG_DRAW_BOXES')) {
				$page->SetFillColor( '#eeeeee' );
				if ($this instanceof kPDFLine) {
					if (defined('PDF_DEBUG_DRAW_LINE_BOXES')) {
						$page->SetLineWidth(0.5);
						$page->SetLineColor( 'yellow' );
						$page->SetFillColor( '#efefef' );
					}
				}
				else {
					$page->SetLineWidth(0.1);
					$page->SetLineColor( '#0000ff' );
				}
//				$page->DrawRectangle($x, $y, $x + $outer_width, $y - $outer_height, kPDFRenderer::SHAPE_DRAW_FILL_AND_STROKE);
				$page->SetFillColor( '#000000' );
				$page->setFont('helvetica', 6);
				if (defined('PDF_DEBUG_DRAW_BOX_INFO') && preg_match(PDF_DEBUG_DRAW_BOX_INFO, $this->Node->Name)) {
					$page->drawText($this->Node->Name . "{$outer_width}x{$outer_height} ".get_class($this), $x+2, $y+8);
				}
			}
		}

		if ($this->GetDisplayLevel() == 'block' && !($this instanceof kPDFLine)) {
			// MARGINS
			if (defined('PDF_DEBUG_DRAW_MARGINS')) {
				$page->SetLineWidth(1);
				$page->SetLineColor( 'green' );
				$page->DrawLine($x, $y, $x + $margin_left, $y);
				$page->DrawLine($x, $y, $x, $y + $margin_bottom);
				$page->SetLineWidth(0.3);
				$page->DrawLine($x + $margin_left, $y, $x + $margin_left, $y + 3);
				$page->DrawLine($x, $y + $margin_top, $x + 3, $y + $margin_top);
			}
			$x += $margin_left;
			$y += $margin_top;

			// BACKGROUND
			// http://manual.prod.intechnic.lv/css21/colors.html#q2
			$body = false;
			if ($this->Node->Name == 'HTML' && $this->GetCSSProperty('background-color') == 'transparent') {
				// get BODY color
				$body = $this->FindChild('BODY');
				if ($body !== false) {
					$background_color = $body->GetCSSProperty('background-color');
					$body->SetCSSProperty('background-color', 'transparent');
				}
			}
			else {
				$background_color = $this->GetCSSProperty('background-color');
			}
			if ($background_color != 'transparent') {
				$page->SetFillColor( $background_color );
				$x1 = $x + 	$border_left +
										$padding_left +
										$width +
										$padding_right +
										$border_right;
				$y1 = $y + ($border_top +
										$padding_top +
										$height +
										$padding_bottom +
										$border_bottom);
				$page->DrawRectangle($x, $y, $x1, $y1, kPDFRenderer::SHAPE_DRAW_FILL);
			}

			// BORDERS
			$x2 = $x + $border_left + $padding_left + $width + $padding_right + $border_right;
			$y2 = $y + ($border_top + $padding_top + $height + $padding_bottom + $border_bottom);

			if ($this->GetCSSProperty('border-top-style') != 'none' && $border_top > 0) {
				$page->SetLineWidth($this->GetCSSProperty('border-top-width'));
				$page->SetLineColor( $this->GetCSSProperty('border-top-color') );
				$page->DrawLine($x+$border_left/2, $y+$border_top/2, $x2-$border_right/2, $y+$border_top/2);
			}
			if ($this->GetCSSProperty('border-right-style') != 'none' && $border_right > 0) {
				$page->SetLineWidth($this->GetCSSProperty('border-right-width'));
				$page->SetLineColor( $this->GetCSSProperty('border-right-color') );
				$page->DrawLine($x2-$border_right/2, $y+$border_top/2, $x2-$border_right/2, $y2-$border_bottom/2);
			}
			if ($this->GetCSSProperty('border-bottom-style') != 'none' && $border_bottom > 0) {
				$page->SetLineWidth($this->GetCSSProperty('border-bottom-width'));
				$page->SetLineColor( $this->GetCSSProperty('border-bottom-color') );
				$page->DrawLine($x+$border_left/2, $y2-$border_bottom/2, $x2-$border_right/2, $y2-$border_bottom/2);
			}
			if ($this->GetCSSProperty('border-left-style') != 'none' && $border_left > 0) {
				$page->SetLineWidth($this->GetCSSProperty('border-left-width'));
				$page->SetLineColor( $this->GetCSSProperty('border-left-color'));
				$page->DrawLine($x+$border_left/2, $y+$border_top/2, $x+$border_left/2, $y2-$border_bottom/2);
			}
			$x += $border_left;
			$y += $border_top;

			// PADDING
			$x += $this->GetCSSProperty('padding-left');
			$y += $this->GetCSSProperty('padding-top');

			if (defined('PDF_DRAW_CONTENT_AREA') && $this->Parent) {
				$page->SetFillColor(  'pink' ) ;
				$page->DrawRectangle($x, $y, $x + $width, $y + $height, kPDFRenderer::SHAPE_DRAW_FILL);
			}

		}

		$this->CurX = $x;
		$this->CurY = $y;
//		$max_width = $this->GetCSSProperty('max-width');
		$this->TopLine = $y;
		$page->SetLineWidth(0.1);

		foreach ($this->Children as $elem)
		{
			$dim = $elem->GetBoxDimensions();
			$align_offset_x = 0;
			$align_offset_y = 0;
			if ($elem->GetDisplayLevel() == 'inline') {
				if ($this->GetCSSProperty('vertical-align') == 'baseline') {
					$align_offset_y = $this->Ascent - $elem->Ascent;
				}
			}
			if (defined('PDF_DEBUG_DRAW_BASELINE')) {
				$page->SetLineWidth(0.5);
				$page->SetLineColor( '#ff0000' );
				$page->DrawLine($this->CurX, $this->CurY + $elem->Ascent, $this->CurX + $dim[0], $this->CurY + $elem->Ascent);
			}
			if ($elem instanceof kPDFLine ) {
				$spacer_w = 0;
				switch ($elem->GetCSSProperty('text-align')) {
					case 'center':
						$align_offset_x = ($dim[0] - $elem->CurX)/2;
						break;
					case 'right':
						$align_offset_x = ($dim[0] - $elem->CurX);
						break;
					case 'justify':
						$spacer_w = $elem->LastLine ? 0 : ($dim[0] - $elem->CurX)/$elem->Spacers;
				}
			}
			if (defined('PDF_DEBUG_DRAW_ELEM_BORDERS')) {
				$page->SetLineWidth(0.1);
				$page->SetLineColor( '#bbbbbb' );
				$page->SetFillColor( '#bbbbbb' );
				$page->setFont('helvetica', 4);
				$page->DrawRectangle($this->CurX, $this->CurY + $align_offset_y, $this->CurX + $dim[0], $this->CurY + $align_offset_y + $dim[1], kPDFRenderer::SHAPE_DRAW_STROKE);
				if (defined('PDF_DEBUG_DRAW_BOX_INFO')  && preg_match(PDF_DEBUG_DRAW_BOX_INFO, $this->Node->Name) && $elem->GetDisplayLevel() != 'block') {
					$page->drawText("w:{$dim[0]}x{$dim[1]}", $this->CurX + 2, $this->CurY + $align_offset_y + 6);
				}
			}
			$tmp_x = $this->CurX;
			$this->CheckPageBreak($page, $elem, $dim);
			$elem->DrawAt($page, $this->CurX + $align_offset_x, $this->CurY + $align_offset_y, $spacer_w);
			if ($elem->GetDisplayLevel() == 'block' && $elem->GetCSSProperty('display') != 'table-cell') {
				$this->CurY += $dim[1];
				$this->CurX = $tmp_x;
			}
			else {
				$this->CurX = $this->CurX + $dim[0] + ($elem->Spacers*$spacer_w);
			}
		}
		if ($this->GetCSSProperty('PAGE-BREAK-AFTER') == 'always') {
			$this->Helper->PDF->NextPage();
			$this->Parent->CurY = $this->Helper->Document->GetCSSProperty('margin-top');
		}
	}

	function CheckPageBreak($page, $elem, $dim)
	{
		if (($this->GetCSSProperty('DISPLAY') != 'block' && $this->GetCSSProperty('DISPLAY') != 'table') || $elem->Node->Name == 'BODY' || $elem->Node->Name == 'HTML') return;

		if ($this->CurY + $dim[1] > $this->Helper->PageBottom) {
			$this->Helper->PDF->NextPage();
			$this->CurY = $this->Helper->Document->FindChild('BODY')->GetCSSProperty('margin-top');
		}
	}

	function GetBoxDimensions()
	{
		return array(
			$this->GetCSSProperty('margin-left') +
			$this->GetCSSProperty('border-left-width') +
			$this->GetCSSProperty('padding-left') +
			$this->GetCSSProperty('width') +
			$this->GetCSSProperty('padding-right') +
			$this->GetCSSProperty('border-right-width') +
			$this->GetCSSProperty('margin-right'),

			$this->GetCSSProperty('margin-top') +
			$this->GetCSSProperty('border-top-width') +
			$this->GetCSSProperty('padding-top') +
			$this->GetCSSProperty('height') +
			$this->GetCSSProperty('padding-bottom') +
			$this->GetCSSProperty('border-bottom-width') +
			$this->GetCSSProperty('margin-bottom'),
		);
	}

	function GetLineBox($first=false) {
		if ($this instanceof kPDFLine ) {
			if ($first) {
				return $this->Parent->FirstChild;
			}
			else {
				return $this;
			}
		}
		if ($this->GetDisplayLevel() == 'block') {
			if ($this->LastChild instanceof kPDFLine ) {
				return $this->LastChild;
			}
			return $this->AddChild( new kPDFLine($this->Node, $this->Helper));
		}

		//in-line box
		return $this->Parent->GetLineBox();
	}

	function GetTable() {
		if ($this instanceof kPDFTable) {
			return $this;
		}
		if ($this->Parent) {
			return $this->Parent->GetTable();
		}
		return false;
	}

	function GetDisplayLevel()
	{
		// http://manual.prod.intechnic.lv/css21/visuren.html#q5
		$display = $this->GetCSSProperty('display');
		if (!$display) $display = $this->CSSSpecifiedProperties['DISPLAY'];
		switch ($display) {
			case 'block':
			case 'list-item':
			case 'table':
			case 'table-cell':
			case 'table-row':
			case 'table':
				return 'block';
			case 'run-in':
				// do special processing here: http://manual.prod.intechnic.lv/css21/visuren.html#run-in
				return 'block';
			case 'none':
				return 'none';
		}
		return 'inline';
	}

	function ProcessCSS()
	{
		$supported_properties = array(
			'DISPLAY',
			'FONT-SIZE', // SHOULD BE FIRST, BECAUSE ALL EM UNITS RELY ON IT
			'WIDTH','MAX-WIDTH','MIN-WIDTH',
			'HEIGHT',
			'COLOR',
			'FONT-FAMILY', 'FONT-WEIGHT', 'FONT-STYLE', 'FONT-VARIANT',
			'WHITE-SPACE',
			'VERTICAL-ALIGN',
			'MARGIN-LEFT', 'MARGIN-RIGHT', 'MARGIN-TOP', 'MARGIN-BOTTOM',
			'BORDER-TOP-STYLE', 'BORDER-RIGHT-STYLE', 'BORDER-BOTTOM-STYLE', 'BORDER-LEFT-STYLE',
			'BORDER-TOP-WIDTH', 'BORDER-RIGHT-WIDTH', 'BORDER-BOTTOM-WIDTH', 'BORDER-LEFT-WIDTH',
			'BORDER-TOP-COLOR', 'BORDER-RIGHT-COLOR', 'BORDER-BOTTOM-COLOR', 'BORDER-LEFT-COLOR',
			'PADDING-TOP', 'PADDING-RIGHT', 'PADDING-BOTTOM', 'PADDING-LEFT',
			'BACKGROUND-COLOR',
			'TEXT-ALIGN', 'TEXT-TRANSFORM',
			'TABLE-LAYOUT',
			'PAGE-BREAK-AFTER'
		);

		$cascade = $this->Helper->Stylesheet->GetAllProperties($this->Node);

		foreach ($supported_properties as $property) {
			$value = false;
			// !!! The following needs to be fixed somehow - thereis no major need in cascading properties for kPDFTextElement and kPDFLine also...
			if (!($this instanceof kPDFTextElement) || ($this instanceof kPDFTextElement && $property != 'DISPLAY')) {
				 $value = isset($cascade[$property]) ? $cascade[$property] : false;
			}
			if (!$value && kCSSDefaults::IsInherited($property)) {
				$value = $this->FindInheritedValue($property);
			}
			if (!$value) {
				$value = kCSSDefaults::GetDefaultValue($property);
			}
			$this->CSSSpecifiedProperties[$property] = $value;
		}
		$this->Node->CssProperties = $this->CSSSpecifiedProperties;
	}

	function FindInheritedValue($property)
	{
		$node = $this->Node;
		if (!$node->Parent) {
			return false;
		}
		do {
			$node = $node->Parent;
			$value = isset($node->ComputedCSSProperties[$property]) ? $node->ComputedCSSProperties[$property] : false;
		} while (!$value && $node->Parent);
		return $value;
	}

	function ComputeCSSProperties()
	{
		foreach ($this->CSSSpecifiedProperties as $property => $value) {
			$this->SetCSSProperty($property, $this->ComputeCSSProperty($property, $value));
		}
		$this->ComputeWidthAndMargins();
		/*if ($this->GetDisplayLevel() ==  'inline') {
			$props = array(
				'margin-top' => 0,
				'margin-right' => 0,
				'margin-bottom' => 0,
				'margin-left' => 0,
				'width' => 0,
			);
			foreach ($props as $name => $value) {
				$this->SetCSSProperty($name, $value);
			}
		}*/
		$this->Node->ComputedCSSProperties = $this->CSSComputedPoperties;
	}

	function ComputeWidthAndMargins()
	{
		if ($this->WidthComputed) return ;

		if ($this->GetDisplayLevel() == 'inline') {
			return;
		}

		if (!$this->Parent) {
			$this->WidthComputed = true;
			return ;
		}
		$cb = $this->GetContainingBlock();
		$cb_width = $cb->GetCSSProperty('width'); // containing block width
		if (!$cb->WidthComputed) { // this means we are inside an element which width is not yet defined (nested TABLE for instance)
			return ;
		}
		if ($this instanceof kPDFLine) { // otherwise margins will be applied from line containing block to the line
			$this->SetCSSProperty('width', $cb_width);
			return;
		}

		// 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
		$width = $this->GetCSSProperty('width'); // may be auto
		$margin_left = $this->GetCSSProperty('margin-left'); // may be auto
		$margin_right = $this->GetCSSProperty('margin-right'); // may be auto
		$border_left = $this->GetCSSProperty('border-left-width');
		$border_right = $this->GetCSSProperty('border-right-width');
		$padding_left = $this->GetCSSProperty('padding-left');
		$padding_right = $this->GetCSSProperty('padding-right');

		if ($width == 'auto' && $this->Parent) {
			if ($margin_left == 'auto') {
				$margin_left = 0;
			}
			if ($margin_right == 'auto') {
				$margin_right = 0;
			}
			$width = $cb_width - $margin_left - $border_left - $padding_left - $padding_right - $border_right - $margin_right;
		}
		elseif ($margin_left != 'auto' && $margin_right != 'auto') { //over-constrained
			$margin_right = $cb_width - $width - $margin_left - $border_left - $padding_left - $padding_right - $border_right;
		}
		elseif ($margin_left == 'auto' && $margin_right == 'auto') {
			$margin_left = ($cb_width - $border_left - $padding_left - $width - $padding_right - $border_right)/2;
			$margin_right = $margin_left;
		}

		$this->SetCSSProperty('width', $width);
		$this->SetCSSProperty('margin-right', $margin_right);
		$this->SetCSSProperty('margin-left', $margin_left);
		$this->WidthComputed = true;
	}

	function ComputeCSSProperty($property, $value)
	{
		if ($property == 'FONT-WEIGHT') {
			if ($value == 'normal') {
				return 400;
			}
			if ($value == 'bold') {
				return 700;
			}
			if ($value == 'bolder') {
				if (!$this->Parent) {
					return 700;
				}
				else {
					return max($this->Parent->GetCSSProperty('font-weight') + 300, 900);
				}
			}
			if ($value == 'lighter') {
				if (!$this->Parent) {
					return 400;
				}
				else {
					return min($this->Parent->GetCSSProperty('font-weight') - 300, 100);
				}
			}
			if (!is_integer($value)) {
				return 400;
			}
			return $value;
		}

		if (preg_match('/([.0-9]+)em/i', $value, $regs)) {
			$cb = $this->GetContainingBlock();
			if ($property == 'FONT-SIZE') {
				if (!$cb) {
					$value = $regs[1] * $this->Helper->PtPerEm;
				}
				else {
					$value = $cb->GetCSSProperty('font-size') * $regs[1];
				}
			}
			else {
				if ($cb) {
					$value = $cb->GetCSSProperty('font-size') * $regs[1];
				}
			}
		}
		elseif (preg_match('/([.0-9]+)(px)/i', $value, $regs)) {
			$value = $regs[1] * 72 / 96;
		}
		elseif (preg_match('/([.0-9]+)(pt)/i', $value, $regs)) {
			$value = $regs[1];
		}
		elseif (preg_match('/([.0-9]+)%/i', $value, $regs)) {
			$cb = $this->GetContainingBlock();
			if ($cb && ($property != 'WIDTH' || ($property == 'WIDTH' && $cb->WidthComputed))) {
				$value = $regs[1]/100 * $cb->GetCSSProperty($property);
			}
		}

		if ($property == 'FONT-SIZE' && preg_match('/(xx-small|x-small|small|medium|large|x-large|xx-large)/i', $value)) {
			switch (strtolower($value)) {
				case 'xx-small':
					return $this->Helper->PtPerEm * 0.5;
				case 'x-small':
					return $this->Helper->PtPerEm * 0.7;
				case 'small':
					return $this->Helper->PtPerEm * 0.8;
				case 'medium':
					return $this->Helper->PtPerEm;
				case 'large':
					return $this->Helper->PtPerEm * 1.2;
				case 'x-large':
					return $this->Helper->PtPerEm * 1.4;
				case 'xx-large':
					return $this->Helper->PtPerEm * 1.6;
			}
		}
//		elseif ($value == 'none') {
//			$value = 0;
//		}
		return $value;
	}

	function GetContainingBlock()
	{
		if (!$this->Parent) return false;
		$parent_display = $this->Parent->GetCSSProperty('display');
		if (preg_match('/^(block|inline-block|table|table-cell|list-item)$/i', $parent_display)) {
			return $this->Parent;
		}
		return $this->Parent->GetContainingBlock();
	}

	function GetCSSProperty($property)
	{
		$property = strtoupper($property);
		return isset($this->CSSComputedPoperties[$property]) ? $this->CSSComputedPoperties[$property] : false;
	}

	function SetCSSProperty($name, $value)
	{
		$this->CSSComputedPoperties[strtoupper($name)] = $value;
	}

	function CheckDimensions()
	{
		$this->ComputeBaseline();
	}

	function ComputeBaseline()
	{
		if (!$this->Parent) return ;
		$display = $this->GetCSSProperty('display');
		/*$dim = $this->GetBoxDimensions();
		$this->Ascent = $dim[1];*/
		if ($display == 'inline' || $display == 'table-cell' || $this instanceof kPDFLine) {
			if ($this->Parent->Ascent < $this->Ascent) {
				$this->Parent->Ascent = $this->Ascent;
			}
			if ($this->Parent->Descent < $this->Descent) {
				$this->Parent->Descent = $this->Descent;
			}
			if ($this->Parent->Gap < $this->Gap) {
				$this->Parent->Gap = $this->Gap;
			}
			$this->Parent->ComputeBaseline();
		}
	}

	function ContentWidthNewLine()
	{
		if ($this->CurLine_MinContentWidth > $this->MinContentWidth) {
			$this->MinContentWidth = $this->CurLine_MinContentWidth;
		}
		if ($this->CurLine_MaxContentWidth > $this->MaxContentWidth) {
			$this->MaxContentWidth = $this->CurLine_MaxContentWidth;
		}
		$this->CurLine_MinContentWidth = 0;
		$this->CurLine_MaxContentWidth = 0;
	}

	function CalcMinMaxContentWidth() {
		if (!$this->Parent) return ;
		$this->ContentWidthNewLine();
		$extra_width =
				$this->GetCSSProperty('margin-left') +
				$this->GetCSSProperty('margin-right') +
				$this->GetCSSProperty('padding-left') +
				$this->GetCSSProperty('padding-right') +
				$this->GetCSSProperty('border-left-width') +
				$this->GetCSSProperty('border-right-width');
//		$extra_width = 0;
		$this->MinContentWidth += $extra_width;
		$this->MaxContentWidth += $extra_width;
		$display = $this->GetCSSProperty('display');

		if (($display == 'inline' || $display == 'table-cell')) {
			$this->Parent->CurLine_MinContentWidth += $this->MinContentWidth;
			$this->Parent->CurLine_MaxContentWidth += $this->MaxContentWidth;
			if ($this->Node->Name == 'BR') {
				$this->Parent->ContentWidthNewLine();
			}
		}
		else {
			if ($this->MinContentWidth > $this->Parent->MinContentWidth) {
				$this->Parent->MinContentWidth = $this->MinContentWidth;
//				$this->Parent->CalcMinMaxContentWidth();
			}
			if ($this->MaxContentWidth > $this->Parent->MaxContentWidth) {
				$this->Parent->MaxContentWidth = $this->MaxContentWidth;
//				$this->Parent->CalcMinMaxContentWidth();
			}
		}
//		$this->ContentWidthNewLine();
	}


}

class kPDFLine extends kPDFElement {
	public $LastLine = true;

	function __construct($node, $helper)
	{
		$line_node = new kXMLNode('_LINE_', array('STYLE' => 'display: block'));
//		$line_node->SetParent($node);
		$node->AddChild($line_node);
		parent::__construct($line_node, $helper);
	}

	function Init()
	{
		parent::Init();
		$this->SetCSSProperty('display', 'block');
		$this->SetCSSProperty('margin-left', '0');
		$this->SetCSSProperty('margin-right', '0');
		$this->SetCSSProperty('margin-top', '0');
		$this->SetCSSProperty('margin-bottom', '0');
		$this->SetCSSProperty('padding-left', '0');
		$this->SetCSSProperty('padding-right', '0');
		$this->SetCSSProperty('padding-top', '0');
		$this->SetCSSProperty('padding-bottom', '0');
	}

}