Skip to content

Instantly share code, notes, and snippets.

@jmc734
Last active December 6, 2018 14:57
Show Gist options
  • Save jmc734/abbc9998f20852838f0c to your computer and use it in GitHub Desktop.
Save jmc734/abbc9998f20852838f0c to your computer and use it in GitHub Desktop.
PHP Windows Printer Library Using the PECL::printer Package

Overview

This library consists of a simple abstract wrapper class for the PECL::printer package and a subclass for printing PDF documents using the Imagick PHP extension.

Examples

Querying Available Printers

print_r(Printer::getPrinters());

Printing a PDF

This snippet will print the document test.pdf to the system printer named "PrimoPDF" and will show up in the spool as "Test Print".

$ptr = new PDFPrinter('PrimoPDF');
$ptr = $ptr->printPDF(__DIR__.'\test.pdf', 'Test Print');

Process

The process the library executes to print a PDF document is as follows:

  1. A new printer resource is created
  2. A new document is created for the printer resource
  3. Each page of the PDF is read into an Imagick object at a given DPI
  4. Each page is written to a temporary file in BMP3 format
  5. A printer page is created for each PDF page and the rendered bitmap is drawn onto the page, optionally using a best fit algorithm
  6. The document is ended and spooled to be printed.

Requirements

  • Windows (pretty much any version)
  • PHP 5+
  • PHP PECL::printer (trunk, compiled from source)
    • Visual C++
    • PHP SDK
  • PHP Imagick Extension
  • ImageMagick
  • GhostScript

Development Setup

The setup used for development was as follows:

  • Windows 7
  • XAMPP 1.8.2-6 (PHP 5.4.31)
  • Microsoft Visual Studio 2008 (VC9)
  • PHP SDK (php-sdk-binary-tools-20110915.zip)
  • PHP PECL::printer
    1. Extract PHP SDK archive to an easily accessible location like C:\php-sdk

    2. Open Windows SDK 6.1 CMD Shell and run the following command:

      setenv /x86 /xp /release
    3. Navigate to your PHP SDK directory and execute the following commands:

      bin\phpsdk_setvars.bat
      bin\phpsdk_buildtree.bat phpdev

      A new directory phpdev should have been created.

    4. Navigate to phpdev\vc9\x86 and extract the PHP Dependencies archive into deps

    5. Use git to clone https://git.php.net/repository/php-src.git into php-src and checkout tag php-5.4.31

    6. Using SVN, checkout http://svn.php.net/viewvc/pecl/printer/trunk/ into pecl/printer

    7. Run the following commands:

      buildconf
      configure --help

      In the help listings, you should now see an option --enable-printer

    8. Run the following command:

      configure --enable-cli --enable-printer=shared

      This configures the Makefile to build the printer package and output a DLL. Now run nmake by simply executing:

      nmake
    9. Once nmake finishes without errors, navigate to php-src\Release_TS and run:

      php -m

      printer should appear in that list.

    10. Copy php-src\Release_TS\php_printer.dll to xampp\php\ext and enable it in php.ini

  • PHP Imagick Extension (PHP 5.4, Thread Safe, php_imagick.zip)
    • Extract and add php_imagick.dll to xampp/php/ext
    • Enable php_imagick.dll in php.ini
  • ImageMagick 6.7.7-3 Q16 (ImageMagick-6.7.7-3-Q16-windows-dll.exe)
    • Make sure the install directory is added to PATH
  • GhostScript 9.09 (gs909w32.exe)
<?php
/**
* Class for handling printing using the pecl/printer extension
*/
abstract class Printer {
const MODE_TEXT = 'TEXT';
const MODE_RAW = 'RAW';
const MODE_EMF = 'EMF';
protected $name;
protected $ptrResource;
protected $documentStarted = false;
protected $pageStarted = false;
/**
* Create a new printer object
* @param string printerName The name of the printer (@see Printer::getPrinters)
* @param string printerMode The mode to set the printer in (@see Printer::MODE_*)
*/
public function __construct($printerName, $printerMode = null) {
$this->name = $printerName;
$this->ptrResource = printer_open($printerName);
if(isset($printerMode)){
$this->setMode($printerMode);
}
}
/**
* Clear the printer's spool queue
*/
public function clearSpool() {
printer_abort($this->ptrResource);
}
/**
* Get the printer DPI
* @return int[] [x-axis DPI, y-axis DPI]
*/
public function getDPI() {
return array(
printer_get_option($this->ptrResource, PRINTER_RESOLUTION_X),
printer_get_option($this->ptrResource, PRINTER_RESOLUTION_Y)
);
}
/**
* Get the current printer paper type
* @return int The paper type (@see PRINTER_FORMAT_*)
*/
public function getPaperType() {
return printer_get_option($this->ptrResource, PRINTER_PAPER_FORMAT);
}
/**
* Set the printer paper type
* @param int type The paper type (@see PRINTER_FORMAT_*)
* @param int width Paper width in dots (if type = PRINTER_FORMAT_CUSTOM)
* @param int length Paper length in dots (if type = PRINTER_FORMAT_CUSTOM)
*/
public function setPaperType($type, $width = null, $length = null){
printer_set_option($this->ptrResource, PRINTER_PAPER_FORMAT, $type);
if($type == PRINTER_FORMAT_CUSTOM){
printer_set_option($this->ptrResource, PRINTER_PAPER_WIDTH, $width);
printer_set_option($this->ptrResource, PRINTER_PAPER_WIDTH, $height);
}
}
/**
* Get the dimensions of the current print paper format in inches
* @return int[] [width in inches, length in inches]
*/
public function getPaperDimensionsInches() {
switch(printer_get_option($this->ptrResource, PRINTER_PAPER_FORMAT)){
case PRINTER_FORMAT_LETTER:
return array(8.5, 11);
case PRINTER_FORMAT_LEGAL:
return array(8.5, 14);
case PRINTER_FORMAT_FOLIO:
return array(8.5, 13);
default:
return array(
printer_get_option($this->ptrResource, PRINTER_PAPER_WIDTH)/25.4,
printer_get_option($this->ptrResource, PRINTER_PAPER_LENGTH)/25.4
);
}
}
/**
* Get the dimensions of the current printer paper format in dots
* @return int[] [width in dots, height in dots]
*/
public function getPaperDimensionsDots() {
$inches = $this->getPaperDimensionsInches();
$dpi = $this->getDPI();
return array(
$inches[0] * $dpi[0],
$inches[1] * $dpi[1],
);
}
/**
* Get the mode of the printer
* @return string The current mode (@see Printer::MODE_*)
*/
protected function getMode() {
return printer_get_option($this->ptrResource, PRINTER_MODE);
}
/**
* Set the mode of the printer
* @param string mode Printer mode (@see Printer::MODE_*)
*/
protected function setMode($mode) {
printer_set_option($this->ptrResource, PRINTER_MODE, $mode);
}
protected function isDocumentStarted() {
return $this->documentStarted;
}
protected function isPageStarted() {
return $this->pageStarted;
}
/**
* Start a new document
* @param string name Name of the new document
* @param bool autoEnd Whether to end the previous document, if there is one
* @throws Exception If a new document couldn't be started or if there is a previously started document and autoEnd is false
*/
protected function startDocument($name, $autoEnd = true) {
if($this->isDocumentStarted()){
if($autoEnd){
$this->endDocument();
} else {
throw new Exception('Previous document is not closed');
}
}
if(!($this->documentStarted = printer_start_doc($this->ptrResource, $name))){
throw new Exception('Failed to start document');
}
}
/**
* End a previously started document
* @param bool force Whether to attempt to close a document regardless if one has been started
* @throws Exception If the document could not be closed
*/
protected function endDocument($force = false) {
if($this->isDocumentStarted() || $force){
if(printer_end_doc($this->ptrResource)){
$this->documentStarted = false;
} else {
throw new Exception('Failed to end document');
}
}
}
/**
* Start a new page
* @param bool autoEnd Whether to end the previous page, if there is one
* @throws Exception If a new page couldn't be started or if there is a previously started page and autoEnd is false
*/
protected function startPage($autoEnd = true) {
if($this->isPageStarted()){
if($autoEnd){
$this->endPage();
} else {
throw new Exception('Previous page is not closed');
}
}
if(!($this->pageStarted = printer_start_page($this->ptrResource))){
throw new Exception('Failed to start page');
}
}
/**
* End a previously started page
* @param bool force Whether to attempt to close a page regardless if one has been started
* @throws Exception If the page could not be closed
*/
protected function endPage($force = false) {
if($this->isPageStarted() || $force){
if(printer_end_page($this->ptrResource)){
$this->pageStarted = false;
} else {
throw new Exception('Failed to end page');
}
}
}
/**
* Draw a bitmap to the current page. Must be a BMP3 file.
* @param string imageFile Path to the image file
* @param int x Dot offset on the x-axis from the top-left corner (optional)
* @param int y Dot offset on the y-axis from the top-left corner (optional)
* @param int width Width of the image in dots (optional)
* @param int height Height of the image in dots (optional)
* @throws Exception If the bitmap couldn't be drawn
*/
protected function drawBMP($imageFile, $x = 0, $y = 0, $width = null, $height = null) {
if(!printer_draw_bmp($this->ptrResource, $imageFile, $x, $y, $width, $height)){
throw new Exception('Could not draw bitmap');
}
}
/**
* Get a list of available printers
* @param int type Printer type to search for (@see PRINTER_ENUM_*, optional)
* @param string name Name of the printer to search for (optional)
* @param int level Printer level to search for (optional)
* @return [][] Array of arrays containing printer names and other info
*/
public static function getPrinters($type = PRINTER_ENUM_LOCAL, $name = '', $level = 1) {
return printer_list($type, $name, $level);
}
}
/**
* Class for printing PDF files using the pecl/printer extension
*/
class PDFPrinter extends Printer {
protected $tempDir;
/**
* Setup the printer
* @param string printerName Printer name
* @param string tempDir Directory to store temporary files in
*/
public function PDFPrinter($printerName, $tempDir = null){
if(!isset($tempDir)){
$tempDir = sys_get_temp_dir();
}
$this->tempDir = $tempDir;
parent::__construct($printerName, self::MODE_RAW);
}
/**
* Print a PDF
* @param string documentFile Path to the PDF to print
* @param string name Name of the document (optional)
* @param int dpi DPI to render the PDF at (optional)
* @throws Exception If there was an issue while printing the PDF
*/
public function printPDF($documentFile, $name = '', $dpi = 300) {
$im = new Imagick();
$im->setResolution($dpi, $dpi);
$im->readImage($documentFile);
$this->startDocument($name);
$numImages = $im->getNumberImages();
$tempName = $this->tempDir.uniqid('ptr_').'.bmp3';
for($i = 0; $i < $numImages; $i++){
$this->startPage();
$im->setIteratorIndex($i);
$im->writeImage($tempName);
$imageGeom = $im->getImageGeometry();
list($x, $y, $width, $height) = $this->calculateBestFit($imageGeom['width'], $imageGeom['height']);
$this->drawBMP($tempName, $x, $y, $width, $height);
$this->endPage();
}
unlink($tempName);
$this->endDocument();
}
/**
* Get the temporary directory
* @return string
*/
public function getTempDir() {
return $this->tempDir;
}
/**
* Set the temporary directory
* @param string
*/
public function setTempDir($tempDir) {
$this->tempDir = $tempDir;
}
/**
* Calculate the optimal drawing dimensions for the given image dimensions
* @param int imageX Width of the image in pixels
* @param int imageY Height of the image in pixels
* @param int[] [x-axis offset in dots, y-axis offset in dots, width in dots, height in dots]
*/
protected function calculateBestFit($imageX, $imageY) {
list($paperX, $paperY) = $this->getPaperDimensionsDots();
$paperAspectRatio = $paperX / $paperY;
$imageAspectRatio = $imageX / $imageY;
if($imageAspectRatio > $paperAspectRatio){
$width = $paperX;
$height = round($paperX / $imageAspectRatio);
$x = 0;
$y = round(($paperY - $height) / 2);
} else {
$width = round($paperY * $imageAspectRatio);
$height = $paperY;
$x = round(($paperX - $width) / 2);
$y = 0;
}
return array($x, $y, $width, $height);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment