Permalink
Browse files

refactoring, and header/footer juju

  • Loading branch information...
1 parent 441fb01 commit c02103d283f6025962b33affa600e57a0c0fd192 @hx committed May 12, 2012
Showing with 365 additions and 37 deletions.
  1. +365 −37 WKPDF.php
View
402 WKPDF.php
@@ -1,6 +1,8 @@
<?php
-class WKPDF implements ArrayAccess
+namespace WKPDF {
+
+class Document implements \ArrayAccess
{
/**
@@ -39,42 +41,52 @@ private static function autoDetectBinaryPath()
}
/**
- * Create a new WKPDF instance from an HTML string.
+ * Create a new Document instance from an HTML string.
* @param string $html The HTML document to be converted to PDF.
- * @return WKPDF
+ * @return Document
*/
public static function fromString($html)
{
- $ret = new self;
- return $ret->setSourceRef($html);
+ return self::fromStringRef($html);
}
/**
- * Create a new WKPDF instance from an HTML string, passed by reference.
+ * Create a new Document instance from an HTML string, passed by reference.
* @param string $html The HTML document to be converted to PDF.
- * @return WKPDF
+ * @return Document
*/
- public static function fromStringReg(&$html)
+ public static function fromStringRef(&$html)
{
$ret = new self;
- return $ret->setSourceRef($html);
+ return $ret->sourceRef($html);
}
/**
- * Create a new WKPDF instance from an HTML file.
+ * Create a new Document instance from an HTML file.
* @param string $path Path of the HTML document to be converted to PDF.
- * @return WKPDF
+ * @return Document
*/
public static function fromFile($path)
{
$ret = new self;
- return $ret->setSourceFile($path);
+ return $ret->sourceFile($path);
}
private static function cleanSwitchName($switchName) {
return preg_replace('`[A-Z]`e', '"-" . strtolower("$0")', preg_replace('`[^-a-zA-Z]`', '', $switchName));
}
+ public static function makeDataUri($data, $type = 'text/html', $charset = null, $base64 = true)
+ {
+ return sprintf(
+ 'data:%s%s%s,%s',
+ $type,
+ $charset ? ";charset=$charset" : '',
+ $base64 ? ';base64' : '',
+ $base64 ? base64_encode($data) : rawurlencode($data)
+ );
+ }
+
private $switches = array(
'encoding' => 'utf-8',
'dpi' => 300,
@@ -112,6 +124,18 @@ private function clearResult()
return $this;
}
+ /**
+ * Set a switch.
+ *
+ * @param string $switch
+ * @param mixed $value
+ * @return Document
+ */
+ public function set($switch, $value)
+ {
+ return $this->offsetSet($switch, $value);
+ }
+
public function offsetExists($offset)
{
return isset($this->switches[self::cleanSwitchName($offset)]);
@@ -143,28 +167,28 @@ public function offsetUnset($offset)
}
}
- public function setSource($html)
+ public function source($html)
{
- return $this->setSourceRef($html);
+ return $this->sourceRef($html);
}
- public function setSourceRef(&$html)
+ public function sourceRef(&$html)
{
$this->sourceFile = '-';
$this->sourceData =& $html;
return $this->clearResult();
}
- public function setSourceFile($path)
+ public function sourceFile($path)
{
if(!is_file($path))
- throw new WKPDFException(sprintf("File '%s' not found.", $path));
+ throw new Exception(sprintf("File '%s' not found.", $path));
$this->sourceFile = realpath($path);
if(!is_readable($this->sourceFile))
- throw new WKPDFException(sprintf("Cannot read file '%s'.", $this->sourceFile));
+ throw new Exception(sprintf("Cannot read file '%s'.", $this->sourceFile));
unset($this->sourceData);
@@ -179,7 +203,7 @@ private function render()
self::$binaryPath = self::autoDetectBinaryPath();
if(!is_executable(self::$binaryPath))
- throw new WKPDFException(sprintf("File '%s' is not executable.", self::$binaryPath));
+ throw new Exception(sprintf("File '%s' is not executable.", self::$binaryPath));
$cmdParts = array(self::$binaryPath);
@@ -255,7 +279,7 @@ private function render()
$exitCode = proc_close($proc);
if($exitCode)
- throw new WKPDFTOHTMLException(
+ throw new BinaryException(
'wkhtmltopdf exited with code ' . $exitCode,
$exitCode,
$results[1]
@@ -273,10 +297,33 @@ public function result()
return $this->render()->result;
}
- public function setMargins($top = null, $right = null, $bottom = null, $left = null)
+ /**
+ * Set margins using CSS shorthand. Numeric values will be treated as
+ * millimeters. Boolean values will leave the value as-is.
+ *
+ * @staticvar array $sides
+ * @param float|integer|string|boolean $top
+ * @param float|integer|string|boolean $right If omitted, will use the value
+ * of <b>$top</b>.
+ * @param float|integer|string|boolean $bottom If omitted, will use the
+ * value of <b>$top</b>.
+ * @param float|integer|string|boolean $left If omitted, will use the value
+ * of <b>$right</b>.
+ * @return Document The Document instance, for chaining.
+ */
+ public function margins($top, $right = null, $bottom = null, $left = null)
{
static $sides = array('top', 'right', 'bottom', 'left');
+ if($right === null)
+ $right = $top;
+
+ if($bottom === null)
+ $bottom = $top;
+
+ if($left === null)
+ $left = $right;
+
foreach($sides as $side)
if(is_numeric($$side))
@@ -289,15 +336,48 @@ public function setMargins($top = null, $right = null, $bottom = null, $left = n
}
- public function setOutline($include = true)
+ public function outline($include = true)
{
$this['no-outline'] = !$include;
$this['outline'] = !!$include;
return $this;
}
- public function setOrientation($orientation = 'Portrait')
+ public function background($include = true)
+ {
+ $this['no-background'] = !$include;
+ $this['background'] = !!$include;
+
+ return $this;
+ }
+
+ public function images($include = true)
+ {
+ $this['no-images'] = !$include;
+ $this['images'] = !!$include;
+
+ return $this;
+ }
+
+ public function cssString($css, $charset = 'utf-8', $useTempFile = false)
+ {
+ $this['user-style-sheet'] = $useTempFile
+ ? TempFile::create($css, 'css')
+ : self::makeDataUri($css, 'text/css', $charset);
+
+ return $this;
+ }
+
+ public function cssFile($path)
+ {
+ if(!is_file($path) || !is_readable($path))
+ throw new Exception("Unable to read file '$path'.");
+
+ $this['user-style-sheet'] = realpath($path);
+ }
+
+ public function orientation($orientation = 'Portrait')
{
$this['orientation'] = (strlen($orientation) && strtolower($orientation{0}) == 'p')
? 'Portrait'
@@ -306,21 +386,21 @@ public function setOrientation($orientation = 'Portrait')
return $this;
}
- public function setPageSize($pageSize = 'A4')
+ public function pageSize($pageSize = 'A4')
{
$this['page-size'] = $pageSize;
return $this;
}
- public function setTitle($title = false)
+ public function title($title = false)
{
$this['title'] = $title;
return $this;
}
- public function setReplace($name, $value)
+ public function replace($name, $value)
{
$a =& $this->headerAndFooterReplacements;
@@ -336,14 +416,16 @@ public function setReplace($name, $value)
private function getHeaderOrFooter($which)
{
if(!isset($this->headerAndFooter[$which]))
- return $this->headerAndFooter[$which] = new WKDFHeaderOrFooter($this, $which);
+ return $this->headerAndFooter[$which] = new HeaderOrFooter($this, explode(' ', $which));
return $this->headerAndFooter[$which];
}
+
+
/**
* Methods to modify the PDF page headers
- * @return WKPDFHeaderOrFooter
+ * @return HeaderOrFooter
*/
public function header()
{
@@ -352,13 +434,22 @@ public function header()
/**
* Methods to modify the PDF page footers
- * @return WKPDFHeaderOrFooter
+ * @return HeaderOrFooter
*/
public function footer()
{
return $this->getHeaderOrFooter('footer');
}
+ /**
+ * Methods to modify the PDF page headers and footers (simultaneously)
+ * @return HeaderOrFooter
+ */
+ public function headerAndFooter()
+ {
+ return $this->getHeaderOrFooter('header footer');
+ }
+
public function serve($asAttachment = false, $fileName = null)
{
header('Content-Type: application/pdf');
@@ -384,31 +475,34 @@ public function serve($asAttachment = false, $fileName = null)
}
}
-class WKDFHeaderOrFooter
+class HeaderOrFooter
{
/**
- * @var WKPDF
+ * @var Document
*/
private $parent;
private $headerOrFooter;
- public function __construct(WKPDF $parent, $headerOrFooter)
+ private $htmlController = null;
+
+ public function __construct(Document $parent, Array $headerOrFooter)
{
$this->parent = $parent;
$this->headerOrFooter = $headerOrFooter;
}
private function setSwitch($switch, $value)
{
- $this->parent[$this->headerOrFooter . '-' . $switch] = $value;
+ foreach($this->headerOrFooter as $i)
+ $this->parent[$i . '-' . $switch] = $value;
return $this;
}
/**
- * @return WKPDF
+ * @return Document
*/
public function end()
{
@@ -460,16 +554,250 @@ public function font($name = 'Arial', $size = 12)
return $this;
}
+
+ public function htmlString($html, $noTempfile = false, $charset = null)
+ {
+ return $this->setSwitch('html', $noTempfile
+ ? Document::makeDataUri($html . '<!--<![CDATA[', 'text/html', $charset)
+ : TempFile::create($html)
+ );
+ }
+
+ public function htmlFile($path)
+ {
+ if(!is_file($path) || !is_readable($path))
+ throw new Exception("Can't read file '$path'.");
+
+ return $this->setSwitch('html', realpath($path));
+ }
+
+ /**
+ * @return HeaderOrFooterHTMLController
+ */
+ public function html()
+ {
+ if($this->htmlController === null)
+ $this->htmlController = new HeaderOrFooterHTMLController($this, $this->headerOrFooter);
+
+ return $this->htmlController;
+ }
}
-class WKPDFException extends Exception { }
+class Exception extends \Exception { }
-class WKPDFTOHTMLException extends WKPDFException
+class BinaryException extends Exception
{
public function __construct($message, $code, $error)
{
parent::__construct($message, $code);
$this->error = $error;
}
public $error;
-}
+}
+
+class TempFile
+{
+
+ public static $tempDir = null;
+
+ private static $pool = array();
+
+ /**
+ *
+ * @param string $contents
+ * @param string $extension
+ * @return TempFile
+ */
+ public static function create($contents, $extension = 'html')
+ {
+ $fileName = sha1($contents) . '.' . $extension;
+
+ return isset(self::$pool[$fileName])
+ ? self::$pool[$fileName]
+ : new self($contents, $fileName);
+ }
+
+ private $contents;
+ private $fileName;
+ private $path = null;
+
+ private function __construct($contents, $fileName)
+ {
+ self::$pool[$fileName] = $this;
+
+ $this->contents =& $contents;
+ $this->fileName = $fileName;
+
+ }
+
+ public function __destruct()
+ {
+ if($this->path)
+ @unlink($this->path);
+ }
+
+ public function path()
+ {
+ if(!$this->path)
+ {
+ if(self::$tempDir === null)
+ foreach(array(sys_get_temp_dir(), dirname(__FILE__)) as $i)
+ if(is_writable($dir = realpath($i) . DIRECTORY_SEPARATOR))
+ {
+ self::$tempDir = $dir;
+ break;
+ }
+
+ if(self::$tempDir === null)
+ throw new Exception("Unable to write to temp dir.");
+
+ if(substr(self::$tempDir, -1) != DIRECTORY_SEPARATOR)
+ self::$tempDir .= DIRECTORY_SEPARATOR;
+
+ file_put_contents($this->path = self::$tempDir . $this->fileName, $this->contents);
+ }
+
+ return $this->path;
+ }
+
+ public function __toString()
+ {
+ return $this->path();
+ }
+}
+
+class HeaderOrFooterHTML
+{
+ private static $template = '<!doctype html>
+<html>
+<head>
+ <meta charset="%s"/>
+ <script>
+ function doSubstitutes() {
+ var html = document.body.innerHTML;
+ document.location.search.substr(1).split("&").forEach(function(x) {
+ x = x.split("=", 2).map(decodeURIComponent);
+ html = html.replace(new RegExp("\\\\[" + x[0] + "]", "g"), x[1]);
+ });
+ document.body.innerHTML = html;
+ }
+ </script>
+ <style>%s</style>
+</head>
+<body onload="doSubstitutes()">
+%s
+</body>
+</html>
+';
+
+ private $charset = 'utf-8';
+ private $body = null;
+ private $css = null;
+
+ public function charset($charset)
+ {
+ $this->charset = $charset;
+ }
+ public function body($html)
+ {
+ $this->body = $html;
+ }
+
+ public function css($css)
+ {
+ $this->css = $css;
+ }
+
+ private function render()
+ {
+ return sprintf(
+ self::$template,
+ $this->charset,
+ $this->css,
+ $this->body
+ );
+ }
+
+ public function __toString()
+ {
+ return (string) TempFile::create($this->render());
+ }
+}
+
+class HeaderOrFooterHTMLController
+{
+ private $parent;
+ private $headerOrFooter;
+
+ public function __construct(HeaderOrFooter $parent, Array $headerOrFooter)
+ {
+ $this->parent = $parent;
+ $this->headerOrFooter = $headerOrFooter;
+ }
+
+ private function setProperty($name, $value)
+ {
+ $document = $this->parent->end();
+
+ foreach($this->headerOrFooter as $headerOrFooter)
+ {
+ $key = $headerOrFooter . '-html';
+
+ if($document[$key] instanceof HeaderOrFooterHTML)
+ $html = $document[$key];
+
+ else
+ $html = $document[$key] = new HeaderOrFooterHTML;
+
+ $html->$name($value);
+ }
+
+ return $this;
+ }
+
+ /**
+ *
+ * @param string $html
+ * @return HeaderOrFooterHTMLController
+ */
+ public function body($html)
+ {
+ return $this->setProperty('body', $html);
+ }
+
+ /**
+ *
+ * @param string $cssRules
+ * @return HeaderOrFooterHTMLController
+ */
+ public function css($cssRules)
+ {
+ return $this->setProperty('css', $cssRules);
+ }
+
+ /**
+ *
+ * @param string $charset
+ * @return HeaderOrFooterHTMLController
+ */
+ public function charset($charset = 'utf-8')
+ {
+ return $this->setProperty('charset', $charset);
+ }
+
+ /**
+ *
+ * @return HeaderOrFooter
+ */
+ public function end()
+ {
+ return $this->parent;
+ }
+}
+
+}
+
+namespace
+{
+ class WKPDFDocument extends \WKPDF\Document { }
+}

0 comments on commit c02103d

Please sign in to comment.