<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Pdf
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: ElementFactory.php 24594 2012-01-05 21:27:01Z matthew $
*/
/** Zend_Pdf_ElementFactory_Interface */
require_once 'Zend/Pdf/ElementFactory/Interface.php';
/**
* PDF element factory.
* Responsibility is to log PDF changes
*
* @package Zend_Pdf
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Pdf_ElementFactory implements Zend_Pdf_ElementFactory_Interface
{
/**
* List of the modified objects.
* Also contains new and removed objects
*
* Array: ojbectNumber => Zend_Pdf_Element_Object
*
* @var array
*/
private $_modifiedObjects = array();
/**
* List of the removed objects
*
* Array: ojbectNumber => Zend_Pdf_Element_Object
*
* @var SplObjectStorage
*/
private $_removedObjects;
/**
* List of registered objects.
* Used for resources clean up when factory is destroyed.
*
* Array of Zend_Pdf_Element objects
*
* @var array
*/
private $_registeredObjects = array();
/**
* PDF object counter.
* Actually it's an object number for new PDF object
*
* @var integer
*/
private $_objectCount;
/**
* List of the attached object factories.
* Array of Zend_Pdf_ElementFactory_Interface objects
*
* @var array
*/
private $_attachedFactories = array();
/**
* Factory internal id
*
* @var integer
*/
private $_factoryId;
/**
* Identity, used for factory id generation
*
* @var integer
*/
private static $_identity = 0;
/**
* Internal cache to save calculated shifts
*
* @var array
*/
private $_shiftCalculationCache = array();
/**
* Object constructor
*
* @param integer $objCount
*/
public function __construct($objCount)
{
$this->_objectCount = (int)$objCount;
$this->_factoryId = self::$_identity++;
$this->_removedObjects = new SplObjectStorage();
}
/**
* Get factory
*
* @return Zend_Pdf_ElementFactory_Interface
*/
public function getFactory()
{
return $this;
}
/**
* Factory generator
*
* @param integer $objCount
* @return Zend_Pdf_ElementFactory_Interface
*/
static public function createFactory($objCount)
{
require_once 'Zend/Pdf/ElementFactory/Proxy.php';
return new Zend_Pdf_ElementFactory_Proxy(new Zend_Pdf_ElementFactory($objCount));
}
/**
* Close factory and clean-up resources
*
* @internal
*/
public function close()
{
$this->_modifiedObjects = null;
$this->_removedObjects = null;
$this->_attachedFactories = null;
foreach ($this->_registeredObjects as $obj) {
$obj->cleanUp();
}
$this->_registeredObjects = null;
}
/**
* Get source factory object
*
* @return Zend_Pdf_ElementFactory
*/
public function resolve()
{
return $this;
}
/**
* Get factory ID
*
* @return integer
*/
public function getId()
{
return $this->_factoryId;
}
/**
* Set object counter
*
* @param integer $objCount
*/
public function setObjectCount($objCount)
{
$this->_objectCount = (int)$objCount;
}
/**
* Get object counter
*
* @return integer
*/
public function getObjectCount()
{
$count = $this->_objectCount;
foreach ($this->_attachedFactories as $attached) {
$count += $attached->getObjectCount() - 1; // -1 as "0" object is a special case and shared between factories
}
return $count;
}
/**
* Attach factory to the current;
*
* @param Zend_Pdf_ElementFactory_Interface $factory
*/
public function attach(Zend_Pdf_ElementFactory_Interface $factory)
{
if ( $factory === $this || isset($this->_attachedFactories[$factory->getId()])) {
/**
* Don't attach factory twice.
* We do not check recusively because of nature of attach operation
* (Pages are always attached to the Documents, Fonts are always attached
* to the pages even if pages already use Document level object factory and so on)
*/
return;
}
$this->_attachedFactories[$factory->getId()] = $factory;
}
/**
* Calculate object enumeration shift.
*
* @param Zend_Pdf_ElementFactory_Interface $factory
* @return integer
*/
public function calculateShift(Zend_Pdf_ElementFactory_Interface $factory)
{
if ($factory === $this) {
return 0;
}
if (isset($this->_shiftCalculationCache[$factory->_factoryId])) {
return $this->_shiftCalculationCache[$factory->_factoryId];
}
$shift = $this->_objectCount - 1;
foreach ($this->_attachedFactories as $subFactory) {
$subFactoryShift = $subFactory->calculateShift($factory);
if ($subFactoryShift != -1) {
// context found
$this->_shiftCalculationCache[$factory->_factoryId] = $shift + $subFactoryShift;
return $shift + $subFactoryShift;
} else {
$shift += $subFactory->getObjectCount()-1;
}
}
$this->_shiftCalculationCache[$factory->_factoryId] = -1;
return -1;
}
/**
* Clean enumeration shift cache.
* Has to be used after PDF render operation to let followed updates be correct.
*/
public function cleanEnumerationShiftCache()
{
$this->_shiftCalculationCache = array();
foreach ($this->_attachedFactories as $attached) {
$attached->cleanEnumerationShiftCache();
}
}
/**
* Retrive object enumeration shift.
*
* @param Zend_Pdf_ElementFactory_Interface $factory
* @return integer
* @throws Zend_Pdf_Exception
*/
public function getEnumerationShift(Zend_Pdf_ElementFactory_Interface $factory)
{
if (($shift = $this->calculateShift($factory)) == -1) {
require_once 'Zend/Pdf/Exception.php';
throw new Zend_Pdf_Exception('Wrong object context');
}
return $shift;
}
/**
* Mark object as modified in context of current factory.
*
* @param Zend_Pdf_Element_Object $obj
* @throws Zend_Pdf_Exception
*/
public function markAsModified(Zend_Pdf_Element_Object $obj)
{
if ($obj->getFactory() !== $this) {
require_once 'Zend/Pdf/Exception.php';
throw new Zend_Pdf_Exception('Object is not generated by this factory');
}
$this->_modifiedObjects[$obj->getObjNum()] = $obj;
}
/**
* Remove object in context of current factory.
*
* @param Zend_Pdf_Element_Object $obj
* @throws Zend_Pdf_Exception
*/
public function remove(Zend_Pdf_Element_Object $obj)
{
if (!$obj->compareFactory($this)) {
require_once 'Zend/Pdf/Exception.php';
throw new Zend_Pdf_Exception('Object is not generated by this factory');
}
$this->_modifiedObjects[$obj->getObjNum()] = $obj;
$this->_removedObjects->attach($obj);
}
/**
* Generate new Zend_Pdf_Element_Object
*
* @todo Reusage of the freed object. It's not a support of new feature, but only improvement.
*
* @param Zend_Pdf_Element $objectValue
* @return Zend_Pdf_Element_Object
*/
public function newObject(Zend_Pdf_Element $objectValue)
{
require_once 'Zend/Pdf/Element/Object.php';
$obj = new Zend_Pdf_Element_Object($objectValue, $this->_objectCount++, 0, $this);
$this->_modifiedObjects[$obj->getObjNum()] = $obj;
return $obj;
}
/**
* Generate new Zend_Pdf_Element_Object_Stream
*
* @todo Reusage of the freed object. It's not a support of new feature, but only improvement.
*
* @param mixed $objectValue
* @return Zend_Pdf_Element_Object_Stream
*/
public function newStreamObject($streamValue)
{
require_once 'Zend/Pdf/Element/Object/Stream.php';
$obj = new Zend_Pdf_Element_Object_Stream($streamValue, $this->_objectCount++, 0, $this);
$this->_modifiedObjects[$obj->getObjNum()] = $obj;
return $obj;
}
/**
* Enumerate modified objects.
* Returns array of Zend_Pdf_UpdateInfoContainer
*
* @param Zend_Pdf_ElementFactory_Interface $rootFactory
* @return array
*/
public function listModifiedObjects($rootFactory = null)
{
if ($rootFactory == null) {
$rootFactory = $this;
$shift = 0;
} else {
$shift = $rootFactory->getEnumerationShift($this);
}
ksort($this->_modifiedObjects);
$result = array();
require_once 'Zend/Pdf/UpdateInfoContainer.php';
foreach ($this->_modifiedObjects as $objNum => $obj) {
if ($this->_removedObjects->contains($obj)) {
$result[$objNum+$shift] = new Zend_Pdf_UpdateInfoContainer($objNum + $shift,
$obj->getGenNum()+1,
true);
} else {
$result[$objNum+$shift] = new Zend_Pdf_UpdateInfoContainer($objNum + $shift,
$obj->getGenNum(),
false,
$obj->dump($rootFactory));
}
}
foreach ($this->_attachedFactories as $factory) {
$result += $factory->listModifiedObjects($rootFactory);
}
return $result;
}
/**
* Register object in the factory
*
* It's used to clear "parent object" referencies when factory is closed and clean up resources
*
* @param string $refString
* @param Zend_Pdf_Element_Object $obj
*/
public function registerObject(Zend_Pdf_Element_Object $obj, $refString)
{
$this->_registeredObjects[$refString] = $obj;
}
/**
* Fetch object specified by reference
*
* @param string $refString
* @return Zend_Pdf_Element_Object|null
*/
public function fetchObject($refString)
{
if (!isset($this->_registeredObjects[$refString])) {
return null;
}
return $this->_registeredObjects[$refString];
}
/**
* Check if PDF file was modified
*
* @return boolean
*/
public function isModified()
{
if (count($this->_modifiedObjects) != 0) {
return true;
}
foreach ($this->_attachedFactories as $subFactory) {
if ($subFactory->isModified()) {
return true;
}
}
return false;
}
}