* @author Charles SANQUER * @author Clement Herreman * @copyright aur1mas * @license http://framework.zend.com/license/new-bsd New BSD License * @see Repository: https://github.com/aur1mas/Wkhtmltopdf * @version 1.10 */ class Wkhtmltopdf { /** * Setters / Getters properties. */ protected $_html = null; protected $_url = null; protected $_orientation = null; protected $_pageSize = null; protected $_toc = false; protected $_copies = 1; protected $_grayscale = false; protected $_title = null; protected $_xvfb = false; protected $_path; // path to directory where to place files protected $_zoom = 1; protected $_headerSpacing; protected $_headerHtml; protected $_footerHtml; protected $_username; protected $_password; protected $_windowStatus; protected $_viewport; protected $_margins = array('top' => null, 'bottom' => null, 'left' => null, 'right' => null); protected $_userStyleSheet = null; // path to user style sheet file protected $_enableSmartShrinking = false; // boolean for smart shrinking, defaults to false protected $_options = array(); /** * Path to executable. */ protected $_bin = '/usr/local/bin/wkhtmltopdf --enable-local-file-access --footer-font-size 7 --footer-right "Page [page] of [topage]" '; protected $_filename = null; // filename in $path directory /** * Available page orientations. */ const ORIENTATION_PORTRAIT = 'Portrait'; // vertical const ORIENTATION_LANDSCAPE = 'Landscape'; // horizontal /** * Page sizes. */ const SIZE_A4 = 'A4'; const SIZE_LETTER = 'letter'; /** * File get modes. */ const MODE_DOWNLOAD = 0; const MODE_STRING = 1; const MODE_EMBEDDED = 2; const MODE_SAVE = 3; /** * @author aur1mas * @param array $options */ public function __construct(array $options = array()) { if (array_key_exists('html', $options)) { $this->setHtml($options['html']); } if (array_key_exists('orientation', $options)) { $this->setOrientation($options['orientation']); } else { $this->setOrientation(self::ORIENTATION_PORTRAIT); } if (array_key_exists('page_size', $options)) { $this->setPageSize($options['page_size']); } else { $this->setPageSize(self::SIZE_A4); } if (array_key_exists('toc', $options)) { $this->setTOC($options['toc']); } if (array_key_exists('margins', $options)) { $this->setMargins($options['margins']); } if (array_key_exists('binpath', $options)) { $this->setBinPath($options['binpath']); } if (array_key_exists('window-status', $options)) { $this->setWindowStatus($options['window-status']); } if (array_key_exists('grayscale', $options)) { $this->setGrayscale($options['grayscale']); } if (array_key_exists('title', $options)) { $this->setTitle($options['title']); } if (array_key_exists('footer_html', $options)) { $this->setFooterHtml($options['footer_html']); } if (array_key_exists('xvfb', $options)) { $this->setRunInVirtualX($options['xvfb']); } if (array_key_exists('user-style-sheet', $options)) { $this->setUserStyleSheet($options['user-style-sheet']); } if (array_key_exists('enable-smart-shrinking', $options)) { $this->setEnableSmartShrinking($options['enable-smart-shrinking']); } if (!array_key_exists('path', $options)) { throw new Exception("Path to directory where to store files is not set"); } if (!is_writable($options['path'])) { throw new Exception("Path to directory where to store files is not writable"); } $this->setPath($options['path']); $this->_createFile(); } /** * Creates file to which will be writen HTML content. * * @author aur1mas * @return string */ protected function _createFile() { do { $this->_filename = $this->getPath() . mt_rand() . '.html'; } while(file_exists($this->_filename)); /** * create an empty file */ file_put_contents($this->_filename, $this->getHtml()); chmod($this->_filename, 0777); return $this->_filename; } /** * Returns file path where HTML content is saved. * * @author aur1mas * @return string */ public function getFilePath() { return $this->_filename; } /** * Executes command. * * @author aur1mas * @param string $cmd command to execute * @param string $input other input (not arguments) * @return array */ protected function _exec($cmd, $input = "") { $result = array('stdout' => '', 'stderr' => '', 'return' => ''); $proc = proc_open($cmd, array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w')), $pipes); /** * We need to asynchronously process streams, as simple sequential stream_get_contents() risks deadlocking if the 2nd pipe's OS pipe buffer fills up before the 1st is fully consumed. * The input is probably subject to the same risk. */ foreach ($pipes as $pipe) { stream_set_blocking($pipe, 0); } $indexPipes = function(array $pipes) { return array_combine(array_map('intval', $pipes), $pipes); }; $allWritables = $indexPipes(array($pipes[0])); $allReadables = $indexPipes(array($pipes[1], $pipes[2])); $readablesNames = array((int)$pipes[1] => 'stdout', (int)$pipes[2] => 'stderr'); do { $readables = $allReadables; $writables = $allWritables; $exceptables = null; $selectTime = microtime(true); $nStreams = stream_select($readables, $writables, $exceptables, null, null); $selectTime = microtime(true) - $selectTime; if ($nStreams === false) { throw new \Exception('Error reading/writing to WKHTMLTOPDF'); } foreach ($writables as $writable) { $nBytes = fwrite($writable, $input); if ($nBytes === false) { throw new \Exception('Error writing to WKHTMLTOPDF'); } if ($nBytes == strlen($input)) { fclose($writable); unset($allWritables[(int)$writable]); $input = ''; } else { $input = substr($input, $nBytes); } } if (count($readables) > 0) { if ($selectTime < 30e3) { usleep(30e3 - $selectTime); // up to 30ms padding, so we don't burn so much time/CPU reading just 1 byte at a time. } foreach ($readables as $readable) { $in = fread($readable, 0x10000); if ($in === false) { throw new \Exception('Error reading from WKHTMLTOPDF '.$readablesNames[$readable]); } $result[$readablesNames[(int)$readable]] .= $in; if (feof($readable)) { fclose($readable); unset($allReadables[(int)$readable]); } } } } while (count($allReadables) > 0 || count($allWritables) > 0); $result['return'] = proc_close($proc); return $result; } /** * Returns help info. * * @author aur1mas * @return string */ public function getHelp() { $r = $this->_exec($this->_bin . " --extended-help"); return $r['stdout']; } /** * Sets the PDF margins. * * @author Clement Herreman * @param $margins array value> The margins. * * Possible : * * top : sets the margin on the top of the PDF * * bottom : sets the margin on the bottom of the PDF * * left : sets the margin on the left of the PDF * * right : sets the margin on the right of the PDF * * Value : size of the margin (positive integer). Null to leave the default one. * @return Wkhtmltopdf $this */ public function setMargins($margins) { $this->_margins = array_merge($this->_margins, $margins); return $this; } /** * Gets the PDF margins. * * @author Clement Herreman * @return array See $this->setMargins() * @see $this->setMargins() */ public function getMargins() { return $this->_margins; } /** * Enables the use of an user style sheet. * * @author Leo Zandvliet * @param string $path * @return Wkthmltopdf */ public function setUserStyleSheet($path) { $this->_userStyleSheet = (string)$path; return $this; } public function getUserStyleSheet() { return $this->_userStyleSheet; } /** * Adds the 'enable-smart-shrinking' option, especially in case it's true. * * @author Leo Zandvliet * @param boolean $value * @return Wkthmltopdf */ public function setEnableSmartShrinking($value) { $this->_enableSmartShrinking = (bool)$value; return $this; } public function getEnableSmartShrinking() { return $this->_enableSmartShrinking; } /** * Sets additional command line options. * * @param $options array