<?php
/**
 * @package dompdf
 * @link    http://www.dompdf.com/
 * @author  Benj Carson <benjcarson@digitaljunkies.ca>
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @version $Id: renderer.cls.php 448 2011-11-13 13:00:03Z fabien.menager $
 */

/**
 * Concrete renderer
 *
 * Instantiates several specific renderers in order to render any given
 * frame.
 *
 * @access private
 * @package dompdf
 */
class Renderer extends Abstract_Renderer {

  /**
   * Array of renderers for specific frame types
   *
   * @var array
   */
  protected $_renderers;
    
  /**
   * Cache of the callbacks array
   * 
   * @var array
   */
  private $_callbacks;
  
  /**
   * true when a stacking context is currently built
   * @var bool
   */
  public static $stacking_first_pass = true;
  
  /**
   * Class destructor
   */
  function __destruct() {
    clear_object($this);
  }
  
  /**
   * Advance the canvas to the next page
   */  
  function new_page() {
    $this->_canvas->new_page();
  }

  /**
   * Render frames recursively
   *
   * @param Frame $frame the frame to render
   */
  function render(Frame $frame, $stacking = false) {
    global $_dompdf_debug;

    if ( $_dompdf_debug ) {
      echo $frame;
      flush();
    }
    
    $style = $frame->get_style();
    
    if ( in_array($style->visibility, array("hidden", "collapse")) ) {
      return;
    }

    $render_self = self::$stacking_first_pass && !$stacking || !self::$stacking_first_pass;
    
    if ( $render_self ) {
      $display = $style->display;
      
      // Starts the CSS transformation
      if ( $style->transform && is_array($style->transform) ) {
        $this->_canvas->save();
        list($x, $y, $w, $h) = $frame->get_padding_box();
        $origin = $style->transform_origin;
        
        foreach($style->transform as $transform) {
          list($function, $values) = $transform;
          if ( $function === "matrix" ) {
            $function = "transform";
          }
          
          $values = array_map("floatval", $values);
          $values[] = $x + $style->length_in_pt($origin[0], $style->width);
          $values[] = $y + $style->length_in_pt($origin[1], $style->height);
          
          call_user_func_array(array($this->_canvas, $function), $values);
        }
      }
    
      switch ($display) {
        
      case "block":
      case "list-item":
      case "inline-block":
      case "table":
      case "inline-table":
        $this->_render_frame("block", $frame);
        break;
  
      case "inline":
        if ( $frame->is_text_node() )
          $this->_render_frame("text", $frame);
        else
          $this->_render_frame("inline", $frame);
        break;
  
      case "table-cell":
        $this->_render_frame("table-cell", $frame);
        break;
  
      case "table-row-group":
      case "table-header-group":
      case "table-footer-group":
        $this->_render_frame("table-row-group", $frame);
        break;
  
      case "-dompdf-list-bullet":
        $this->_render_frame("list-bullet", $frame);
        break;
  
      case "-dompdf-image":
        $this->_render_frame("image", $frame);
        break;
        
      case "none":
        $node = $frame->get_node();
            
        if ( $node->nodeName === "script" ) {
          if ( $node->getAttribute("type") === "text/php" ||
               $node->getAttribute("language") === "php" ) {
            // Evaluate embedded php scripts
            $this->_render_frame("php", $frame);
          }
          
          elseif ( $node->getAttribute("type") === "text/javascript" ||
               $node->getAttribute("language") === "javascript" ) {
            // Insert JavaScript
            $this->_render_frame("javascript", $frame);
          }
        }
  
        // Don't render children, so skip to next iter
        return;
        
      default:
        break;
  
      }
  
      // Check for begin frame callback
      $this->_check_callbacks("begin_frame", $frame);
      
      // Starts the overflow: hidden box
      if ( $style->overflow === "hidden" ) {
        list($x, $y, $w, $h) = $frame->get_padding_box();
        $this->_canvas->clipping_rectangle($x, $y, $w, $h);
      }
    }
  
    $page = $frame->get_root()->get_reflower();
    
    foreach ($frame->get_children() as $child) {
      $child_style = $child->get_style();
      $_stacking = $stacking;
      
      // Stacking context
      if ( self::$stacking_first_pass && (
           $child_style->z_index !== "auto" || 
           $child_style->float !== "none" || 
           $child->is_positionned()) ) {
        $z_index = ($child_style->z_index === "auto") ? 0 : intval($child_style->z_index);
        $page->add_frame_to_stacking_context($child, $z_index);
        $_stacking = true;
      }
      
      $this->render($child, $_stacking);
    }
     
    if ( $render_self ) {
      // Ends the overflow: hidden box
      if ( $style->overflow === "hidden" ) {
        $this->_canvas->clipping_end();
      }
  
      if ( $style->transform && is_array($style->transform) ) {
        $this->_canvas->restore();
      }
  
      // Check for end frame callback
      $this->_check_callbacks("end_frame", $frame);
    }
  }
  
  /**
   * Check for callbacks that need to be performed when a given event
   * gets triggered on a frame
   *
   * @param string $event the type of event
   * @param Frame $frame the frame that event is triggered on
   */
  protected function _check_callbacks($event, $frame) {
    if (!isset($this->_callbacks)) {
      $this->_callbacks = $this->_dompdf->get_callbacks();
    }
    
    if (is_array($this->_callbacks) && isset($this->_callbacks[$event])) {
      $info = array(0 => $this->_canvas, "canvas" => $this->_canvas,
                    1 => $frame, "frame" => $frame);
      $fs = $this->_callbacks[$event];
      foreach ($fs as $f) {
        if (is_callable($f)) {
          if (is_array($f)) {
            $f[0]->$f[1]($info);
          } else {
            $f($info);
          }
        }
      }
    }
  }

  /**
   * Render a single frame
   *
   * Creates Renderer objects on demand
   *
   * @param string $type type of renderer to use
   * @param Frame $frame the frame to render
   */
  protected function _render_frame($type, $frame) {

    if ( !isset($this->_renderers[$type]) ) {
      
      switch ($type) {
      case "block":
        $this->_renderers[$type] = new Block_Renderer($this->_dompdf);
        break;

      case "inline":
        $this->_renderers[$type] = new Inline_Renderer($this->_dompdf);
        break;

      case "text":
        $this->_renderers[$type] = new Text_Renderer($this->_dompdf);
        break;

      case "image":
        $this->_renderers[$type] = new Image_Renderer($this->_dompdf);
        break;
      
      case "table-cell":
        $this->_renderers[$type] = new Table_Cell_Renderer($this->_dompdf);
        break;
      
      case "table-row-group":
        $this->_renderers[$type] = new Table_Row_Group_Renderer($this->_dompdf);
        break;

      case "list-bullet":
        $this->_renderers[$type] = new List_Bullet_Renderer($this->_dompdf);
        break;

      case "php":
        $this->_renderers[$type] = new PHP_Evaluator($this->_canvas);
        break;

      case "javascript":
        $this->_renderers[$type] = new Javascript_Embedder($this->_dompdf);
        break;
        
      }
    }
    
    $this->_renderers[$type]->render($frame);

  }
}
