blob: f46ba5a2365d0541a5a907267b6ddefb1d8b9980 [file] [log] [blame]
<?php
/**
* File containing the ezcDocumentPdfTcpdfDriver class
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Document
* @version //autogen//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* Pdf driver based on TCPDF
*
* TCPDF is a PHP based PDF renderer, originally based on FPDF and available at
* http://tcpdf.org.
*
* The TCPDF class has to be loaded before this driver can be used. TCPDF has
* some bad coding practices, like:
* - Throws lots of warnings and notices, which you might want to silence by
* temporarily changing the error reporting level
* - Reads and writes several global variables, which might or might not
* interfere with your application code
* - Uses eval() in several places, which results in non-cacheable OP-Codes.
*
* On the other hand TCPDF can handle UTF-8 just fine, and therefore supports a
* braod range of unicode characters.
*
* The driver can be used by setting the respective option on the
* PDF document wrapper, you need to download and load the TCPDF implementation
* yourself.
*
* <code>
* // Load the docbook document and create a PDF from it
* $pdf = new ezcDocumentPdf();
*
* include '/path/to/tcpdf.php';
* $pdf->options->driver = new ezcDocumentPdfTcpdfDriver();
* $pdf->createFromDocbook( $docbook );
* file_put_contents( __FILE__ . '.pdf', $pdf );
* </code>
*
* @package Document
* @version //autogen//
*/
class ezcDocumentPdfTcpdfDriver extends ezcDocumentPdfDriver
{
/**
* Tcpdf Document instance
*
* @var Tcpdf
*/
protected $document;
/**
* Page instances, given as an array, indexed by their page number starting
* with 0.
*
* @var array
*/
protected $pages;
/**
* Internal targets
*
* Target objects for all rendered internal targets, to be used when
* rendering the internal links at the end of the processing.
*
* @var array
*/
protected $internalTargets = array();
/**
* Internal targets
*
* Link identifiers groupd by target names for all links in the document,
* which should be associated with the target at the end of the document
* rendering.
*
* @var array
*/
protected $internalLinkSources = array();
/**
* Array with fonts, and their equivalents for bold and italic markup. This
* array will be extended when loading new fonts, but contains the builtin
* fonts by default.
*
* The fourth value for each font is bold + oblique, the index is the
* bitwise and combination of the repective combinations. Each font MUST
* have at least a value for FONT_PLAIN assigned.
*
* @var array
*/
protected $fonts = array(
'sans-serif' => array(
self::FONT_PLAIN => 'helvetica',
self::FONT_BOLD => 'helveticab',
self::FONT_OBLIQUE => 'helveticai',
3 => 'helveticabi',
),
'serif' => array(
self::FONT_PLAIN => 'times',
self::FONT_BOLD => 'timesb',
self::FONT_OBLIQUE => 'timesi',
3 => 'timesbi',
),
'monospace' => array(
self::FONT_PLAIN => 'courier',
self::FONT_BOLD => 'courierb',
self::FONT_OBLIQUE => 'courieri',
3 => 'courierbi',
),
'Symbol' => array(
self::FONT_PLAIN => 'symbol',
),
'ZapfDingbats' => array(
self::FONT_PLAIN => 'zapfdingbats',
),
);
/**
* Reference to the page currently rendered on
*
* @var haruPage
*/
protected $currentpage;
/**
* Mapping of native permission flags, to Haru permission flags
*
* @var array
*/
protected $permissionMapping = array(
ezcDocumentPdfOptions::EDIT => 'annot-forms',
ezcDocumentPdfOptions::PRINTABLE => 'print',
ezcDocumentPdfOptions::COPY => 'copy',
ezcDocumentPdfOptions::MODIFY => 'modify',
);
/**
* Name and style of default font / currently used font
*
* @var array
*/
protected $currentFont = array(
'name' => 'sans-serif',
'style' => self::FONT_PLAIN,
'size' => 12,
);
/**
* Construct driver
*
* Creates a new document instance maintaining all document context.
*
* @return void
*/
public function __construct()
{
parent::__construct();
// Do nothing in here, we can only instantiate the document on first
// page creation, because we do not know about the page format
// beforehand.
$this->pages = array();
$this->document = null;
}
/**
* Initialize haru documents
*
* @return void
*/
protected function initialize()
{
// Sorry for this, but we need it to prevent from warnings in TCPDF:
$GLOBALS['utf8tolatin'] = array();
// Create the basic document, which dimensions should not matter, since
// we specify this at each page creation separetely.
$this->document = new TCPDF(
'P', // Portrait size, which should notter sinc we provide the exact size
'mm', // Units used for all values, except font sizes
array( 1000, 1000 ),
true, // Use unicode
'UTF-8'
);
// We do this ourselves
$this->document->setAutoPageBreak( false );
$this->document->setMargins( 0, 0 );
$this->document->setCreator( 'eZ Components - Document //autogen//' );
$this->document->setPrintHeader( false );
$this->document->setPrintFooter( false );
$this->document->setCompression( $this->options->compress );
if ( $this->options->ownerPassword !== null )
{
$this->setPermissions( $this->options );
}
$this->document->setFont(
$this->fonts[$this->currentFont['name']][self::FONT_PLAIN],
'', // Style
$this->currentFont['size']
);
}
/**
* Set permissions for PDF document
*
* @param int $permissions
* @return void
*/
protected function setPermissions( ezcDocumentPdfOptions $options )
{
$flag = array();
foreach ( $this->permissionMapping as $own => $tcpdf )
{
if ( $options->permissions & $own )
{
$flag[] = $tcpdf;
}
}
$this->document->setProtection( $flag, $this->options->userPassword, $this->options->ownerPassword );
}
/**
* Create a new page
*
* Create a new page in the PDF document with the given width and height.
*
* @param float $width
* @param float $height
* @return void
*/
public function createPage( $width, $height )
{
if ( $this->document === null )
{
$this->initialize();
}
// Create a new page, and create a reference in the pages array
$this->document->AddPage( 'P', array( $width, $height ) );
$this->pages[] = $this->document->getPage();
}
/**
* Try to set font
*
* Stays with the old font, if the newly specified font is not available.
*
* If the font does not support the given style, it falls back to the style
* used beforehand, and if this is also not support the plain style will be
* used.
*
* @param string $name
* @param int $style
* @return void
*/
public function trySetFont( $name, $style )
{
if ( $this->document === null )
{
$this->initialize();
}
// Just du no use new font, if it is unknown
if ( !isset( $this->fonts[$name] ) )
{
throw new ezcDocumentInvalidFontException( $name );
}
// Style fallback
if ( !isset( $this->fonts[$name][$style] ) )
{
$style = isset( $this->fonts[$name][$this->currentFont['style']] ) ? $this->currentFont['style'] : self::FONT_PLAIN;
}
// Create and use font on current page
$this->document->setFont(
$this->fonts[$name][self::FONT_PLAIN],
( $style & self::FONT_BOLD ? 'B' : '' ) .
( $style & self::FONT_OBLIQUE ? 'I' : '' )
);
$this->currentFont = array(
'name' => $name,
'style' => $style,
'size' => $this->currentFont['size'],
);
}
/**
* Set text formatting option
*
* Set a text formatting option. The names of the options are the same used
* in the PCSS files and need to be translated by the driver to the proper
* backend calls.
*
*
* @param string $type
* @param mixed $value
* @return void
*/
public function setTextFormatting( $type, $value )
{
switch ( $type )
{
case 'font-style':
if ( ( $value === 'oblique' ) ||
( $value === 'italic' ) )
{
$this->trySetFont(
$this->currentFont['name'],
$this->currentFont['style'] | self::FONT_OBLIQUE
);
}
else
{
$this->trySetFont(
$this->currentFont['name'],
$this->currentFont['style'] & ~self::FONT_OBLIQUE
);
}
break;
case 'font-weight':
if ( ( $value === 'bold' ) ||
( $value === 'bolder' ) )
{
$this->trySetFont(
$this->currentFont['name'],
$this->currentFont['style'] | self::FONT_BOLD
);
}
else
{
$this->trySetFont(
$this->currentFont['name'],
$this->currentFont['style'] & ~self::FONT_BOLD
);
}
break;
case 'font-family':
$this->trySetFont( $value, $this->currentFont['style'] );
break;
case 'font-size':
$this->currentFont['size'] = ezcDocumentPcssMeasure::create( $value )->get( 'pt' );
$this->document->setFontSize( $this->currentFont['size'] );
break;
case 'color':
$this->document->setTextColor(
$value['red'] * 255,
$value['green'] * 255,
$value['blue'] * 255
);
default:
// @todo: Error reporting.
}
}
/**
* Calculate the rendered width of the current word
*
* Calculate the width of the passed word, using the currently set text
* formatting options.
*
* @param string $word
* @return float
*/
public function calculateWordWidth( $word )
{
if ( $this->document === null )
{
$this->initialize();
}
return $this->document->GetStringWidth( $word );
}
/**
* Get current line height
*
* Return the current line height in millimeter based on the current font
* and text rendering settings.
*
* @return float
*/
public function getCurrentLineHeight()
{
return ezcDocumentPcssMeasure::create( $this->currentFont['size'] . 'pt' )->get();
}
/**
* Draw word at given position
*
* Draw the given word at the given position using the currently set text
* formatting options.
*
* The coordinate specifies the left bottom edge of the words bounding box.
*
* @param float $x
* @param float $y
* @param string $word
* @return void
*/
public function drawWord( $x, $y, $word )
{
$size = ezcDocumentPcssMeasure::create( $this->currentFont['size'] . 'pt' )->get();
$this->document->setXY( $x, (float) round( $y - $size, 4 ) );
$this->document->Write( $size, $word );
}
/**
* Draw image
*
* Draw image at the defined position. The first parameter is the
* (absolute) path to the image file, and the second defines the type of
* the image. If the driver cannot handle this aprticular image type, it
* should throw an exception.
*
* The further parameters define the location where the image should be
* rendered and the dimensions of the image in the rendered output. The
* dimensions do not neccesarily match the real image dimensions, and might
* require some kind of scaling inside the driver depending on the used
* backend.
*
* @param string $file
* @param string $type
* @param float $x
* @param float $y
* @param float $width
* @param float $height
* @return void
*/
public function drawImage( $file, $type, $x, $y, $width, $height )
{
switch ( $type )
{
case 'image/png':
$type = 'PNG';
break;
case 'image/jpeg':
$type = 'JPEG';
break;
default:
throw new ezcBaseFunctionalityNotSupportedException( $type, 'Image type not supported by TCPDF' );
}
$this->document->Image( $file, $x, $y, $width, $height, $type );
}
/**
* Transform points array into a TCPDF points array.
*
* @param array $points
* @return array
*/
protected function getPointsArray( array $points )
{
$tPoints = array();
foreach ( $points as $point )
{
$tPoints[] = $point[0];
$tPoints[] = $point[1];
}
return $tPoints;
}
/**
* Draw a fileld polygon
*
* Draw any filled polygon, filled using the defined color. The color
* should be passed as an array with the keys "red", "green", "blue" and
* optionally "alpha". Each key should have a value between 0 and 1
* associated.
*
* The polygon itself is specified as an array of two-tuples, specifying
* the x and y coordinate of the point.
*
* @param array $points
* @param array $color
* @return void
*/
public function drawPolygon( array $points, array $color )
{
$this->document->polygon(
$this->getPointsArray( $points ),
'F', // Only filled polygon, no border
array(), // Line style
array(
'r' => $color['red'] * 255,
'g' => $color['green'] * 255,
'b' => $color['blue'] * 255,
)
);
}
/**
* Draw a polyline
*
* Draw any non-filled polygon, filled using the defined color. The color
* should be passed as an array with the keys "red", "green", "blue" and
* optionally "alpha". Each key should have a value between 0 and 1
* associated.
*
* The polyline itself is specified as an array of two-tuples, specifying
* the x and y coordinate of the point.
*
* The thrid parameter defines the width of the border and the last
* parameter may optionally be set to false to not close the polygon (draw
* another line from the last point to the first one).
*
* @param array $points
* @param array $color
* @param float $width
* @param bool $close
* @return void
*/
public function drawPolyline( array $points, array $color, $width, $close = true )
{
$style = array(
'width' => $width,
'color' => array(
'r' => $color['red'] * 255,
'g' => $color['green'] * 255,
'b' => $color['blue'] * 255,
),
);
// Draw all lines of the polygon. We cannot use the "polygon()" method
// in TCPDF, because it _always_ closes the polygon.
$last = null;
foreach ( $points as $point )
{
if ( $last !== null )
{
$this->document->line( $last[0], $last[1], $point[0], $point[1], $style );
}
$last = $point;
}
// Draw closing line in polygon
if ( $close )
{
$first = reset( $points );
$this->document->line( $last[0], $last[1], $first[0], $first[1], $style );
}
}
/**
* Add an external link
*
* Add an external link to the rectangle specified by its top-left
* position, width and height. The last parameter is the actual URL to link
* to.
*
* @param float $x
* @param float $y
* @param float $width
* @param float $height
* @param string $url
* @return void
*/
public function addExternalLink( $x, $y, $width, $height, $url )
{
$this->document->link( $x, $y, $width, $height, $url );
}
/**
* Add an internal link
*
* Add an internal link to the rectangle specified by its top-left
* position, width and height. The last parameter is the target identifier
* to link to.
*
* @param float $x
* @param float $y
* @param float $width
* @param float $height
* @param string $target
* @return void
*/
public function addInternalLink( $x, $y, $width, $height, $target )
{
$this->document->link( $x, $y, $width, $height, $link = $this->document->addLink() );
if ( !isset( $this->internalLinkSources[$target] ) )
{
$this->internalLinkSources[$target] = array( $link );
}
else
{
$this->internalLinkSources[$target][] = $link;
}
}
/**
* Add an internal link target
*
* Add an internal link to the current page. The last parameter
* is the target identifier.
*
* @param string $id
* @return void
*/
public function addInternalLinkTarget( $id )
{
$this->internalTargets[$id] = $this->document->getPage();
}
/**
* Render internal links
*
* Link identifiers groupd by target names for all links in the document,
* which should be associated with the target at the end of the document
* rendering.
*
* @return void
*/
protected function renderInternalLinks()
{
foreach ( $this->internalLinkSources as $target => $links )
{
if ( !isset( $this->internalTargets[$target] ) )
{
// No target defined for these links
continue;
}
foreach ( $links as $link )
{
$this->document->setLink( $link, 0, $this->internalTargets[$target] );
}
}
}
/**
* Register a font
*
* Registers a font, which can be used by its name later in the driver. The
* given type is either self::FONT_PLAIN or a bitwise combination of self::FONT_BOLD
* and self::FONT_OBLIQUE.
*
* The third paramater specifies an array of pathes with references to font
* definition files. Multiple pathes may be specified to provide the same
* font using different types, because not all drivers may process all font
* types.
*
* @param string $name
* @param int $type
* @param array $pathes
* @return void
*/
public function registerFont( $name, $type, array $pathes )
{
throw new ezcBaseFunctionalityNotSupportedException( 'Loading fonts', 'TCPDF does not support foreign fonts.' );
}
/**
* Set metadata
*
* Set document meta data. The meta data types are identified by a list of
* keys, common to PDF, like: title, author, subject, created, modified.
*
* The values are passed like embedded in the docbook document and might
* need to be reformatted.
*
* @param string $key
* @param string $value
* @return void
*/
public function setMetaData( $key, $value )
{
if ( $this->document === null )
{
$this->initialize();
}
switch ( $key )
{
case 'title':
$this->document->setTitle( $value );
break;
case 'author':
$this->document->setAuthor( $value );
break;
case 'subject':
$this->document->setSubject( $value );
break;
case 'created':
case 'modified':
// Date information cannot be set with TCPDF.
break;
}
}
/**
* Generate and return PDF
*
* Return the generated binary PDF content as a string.
*
* @return string
*/
public function save()
{
$this->renderInternalLinks();
return $this->document->Output( 'ignored', 'S' );
}
}
?>