diff --git a/include/ezcomponents/Base/CREDITS b/include/ezcomponents/Base/CREDITS new file mode 100644 index 000000000..2cc9fc273 --- /dev/null +++ b/include/ezcomponents/Base/CREDITS @@ -0,0 +1,16 @@ +CREDITS +======= + +eZ Components team +------------------ + +- Sergey Alexeev +- Sebastian Bergmann +- Jan Borsodi +- Raymond Bosman +- Frederik Holljen +- Kore Nordmann +- Derick Rethans +- Vadym Savchuk +- Tobias Schlitt +- Alexandru Stanoi diff --git a/include/ezcomponents/Base/ChangeLog b/include/ezcomponents/Base/ChangeLog new file mode 100644 index 000000000..f57c75cb3 --- /dev/null +++ b/include/ezcomponents/Base/ChangeLog @@ -0,0 +1,251 @@ +1.5 - Monday 16 June 2008 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes. + + +1.5rc1 - Tuesday 10 June 2008 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes + + +1.5beta1 - Tuesday 27 May 2008 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added the ezcBasePersistable interface that can be used to ensure that the + object implementing this interface can be used with PersistentObject and + Search. + + +1.5alpha2 - Tuesday 13 May 2008 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed a bug in ezcBaseFile::findRecursive that prevented you from passing an + empty array to collect statistics. +- Changed ezcBase::getInstallationPath() so that it always returns a trailing + directory separator. + + +1.5alpha1 - Monday 07 April 2008 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Implemented issue #8529: Added a by-reference argument to + ezcBaseFile::findRecursive that returns statistsics (count and total size) + of all files that are returned by this function. +- Implemented issue #11506: Added the static method + ezcBase::getInstallationPath(). +- Implemented issue #12694: replace reflection test for class type with spl + function. + + +1.4.1 - Monday 14 January 2008 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #11448: ezc_bootsrap.php uses relative paths. +- Fixed issue #12316: Numbers in own component prefix not possible. +- Fixed issue #12329: ezcBaseFeatures::findExecutableInPath's return value + does not include the extension to the executable at the end on Windows. +- Added an optional argument to the ezcBaseValueException constructor to allow + the exception to be used for non-property/setting type violations as well. + + +1.4 - Monday 17 December 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes. + + +1.4rc1 - Wednesday 05 December 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes. + + +1.4beta1 - Wednesday 28 November 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes. + + +1.4alpha2 - Monday 29 October 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added the ezcBaseFile::copyRecursive() method, to recursively copy files or + directories +- Fixed issue #11540: Problems with ezcFile::findRecursive and + ezcFile::calculateRelativePath on systems where DIRECTORY_SEPERATOR is not + //. + + +1.4alpha1 - Tuesday 18 September 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added the ezcBaseFile class, which was moved from the File component. +- Added the ezcBaseFile::isAbsolutePath() method, which returns whether a path + is absolute or relative. + + +1.3.1 - Monday 30 July 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #11057: The ezcBaseConfigurationInitializer inteface is not + enforced for callback classes. + + +1.3 - Monday 02 July 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Documentation fixes and updates. + + +1.3rc1 - Monday 25 June 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Documentation fixes and updates. + + +1.3beta2 - Thursday 31 May 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #10704: Autoload fails on class not found. The exception is now + off by default, but can be turned on through the "debug" property of the + ezcBaseAutoloadOptions class. This option class can be set with + ezcBase::setOptions(). + + +1.3beta1 - Monday 07 May 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #8433: ezcBase::getRepositoryDirectories() problems. +- Fixed issue #10583: ezcBaseOptions misses __isset(). +- Fixed issue #10666: ezc_bootstrap.php fails on Windows. +- Implemented issue #9569: Add "autoload.php" as 3rd fallback autoload file to + search for. +- Implemented issue #9988: Implement component preloading for better opcode + cache performance. +- Added exception class ezcBaseExtensionNotFoundException to be thrown when an + extension is required but is not found. +- Changed the ezcBaseInit::fetchConfig() method to return the value that was + returned from the callback function. + + +1.2 - Monday 18 December 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #9658: Checking if $_ENV['PATH'] is set before using it in + ezcBaseFeatures. +- Fixed issue #9780: ezcBaseFeatures throws notice about non-existing array + key "PATH". +- Fixed issue #9819: Let all components deal with the ezcBaseAutoloadException + properly. +- Fixed the exception name for 'ezcBaseDoubleClassRepositoryPrefix' - it was + missing "Exception". +- Implemented issue #9811: If a file for a class can not be found through + autoloading, we now throw the ezcBaseAutoloadException which makes debugging + easier. +- Added the static method ezcBaseFeatures::findExecutableInPath() that searches the + path for the given executable. +- Added the static method ezcBaseFeatures::os() that returns a sanitized + version of the current OS' name. + + +1.2beta2 - Monday 20 November 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #8507: Two autoload directories with the same basepath don't + work. +- Fixed issue #9390: Classes in external repositories that map to the same + autoload filename of an internal component were added to the external + autoload cache array as well. + + +1.2beta1 - Tuesday 24 October 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added the ezcBaseFeatures class to check whether the current PHP + installation and environment provides features that can be used in the + components. +- Added the ezcBaseInit class that assists you by setting up on-demand + configurations for objects (most notable useful for singleton classes). +- Implemented FR #8508: Display search paths for the autoload files in case of + a missing class. +- Implemented FR #8753: Added the 'Base/ezc_bootstrap.php' file which sets up + the autoload environment for you to facilitate an easier way of starting to + use the eZ components. + + +1.1.1 - Monday 28 August 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added the ezcBaseStruct class from which all structs in all components + should inherit from. + + +1.1 - Friday 09 June 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed bug #8434: ezcBase autoload system does not handle classes without a + prefix. +- Fixed bug #8435: ezcBase::addClassRepository assumes the ezc way of + structuring files. From now on the path specifying the autoload directory is + *not* relative to the repository directory anymore. + + +1.1rc1 - Monday 29 May 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed bug #8252: Autoloading for external repositories only works for the + first such class. + + +1.1beta2 - Tuesday 09 May 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added support for external class repositories. You can now add a class + repository to the autoload mechanism by using the addClassRepository() + method. +- Added a method to return all configured class repositories. +- Added the REMOVE constant to the ezcBaseFileException. +- Added the ezcBaseOptions class that serves as base class for all option + classes in the components. + + +1.1beta1 - Wednesday 19 April 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Changed the way how files are included when the SVN checkout of the eZ + components was used. This does not affect normal use of the components. +- Fixed class descriptions for the exceptions in the documentation. + + +1.0 - Monday 30 January 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added HTML escaping of exception messages so that they show up correctly in + a browser. The original message is stored in the originalMessage property + in the exception object. + + +1.0rc1 - Monday 16 January 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added the ezcBaseException that all exceptions in the components library + should descent from. +- Added generic File and IO exceptions that all other components can use + instead of having to reimplement them. +- Added ezcBase::checkDependency() method that allows components to specify + dependencies on either a PHP version or a PHP extension. + + +1.0beta2 - Wednesday 21 December 2005 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added the ezcBasePropertyException that can be used by components to signal + that an property was assigned a value which it does not allows. + + +1.0beta1 - Tuesday 22 November 2005 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Initial release of this package. diff --git a/include/ezcomponents/Base/DESCRIPTION b/include/ezcomponents/Base/DESCRIPTION new file mode 100644 index 000000000..170a06566 --- /dev/null +++ b/include/ezcomponents/Base/DESCRIPTION @@ -0,0 +1,2 @@ +The Base package provides the basic infrastructure that all packages rely on. +Therefore every component relies on this package. diff --git a/include/ezcomponents/Base/design/class_diagram.png b/include/ezcomponents/Base/design/class_diagram.png new file mode 100644 index 000000000..2415977b5 Binary files /dev/null and b/include/ezcomponents/Base/design/class_diagram.png differ diff --git a/include/ezcomponents/Base/design/design.txt b/include/ezcomponents/Base/design/design.txt new file mode 100644 index 000000000..72a080dc4 --- /dev/null +++ b/include/ezcomponents/Base/design/design.txt @@ -0,0 +1,9 @@ +Base +==== + +Purpose +------- +This is the base package of the eZ publish components, offering the basic +support that all Components need. In the first version this will be the +autoload support. + diff --git a/include/ezcomponents/Base/docs/repos/Me/myclass1.php b/include/ezcomponents/Base/docs/repos/Me/myclass1.php new file mode 100644 index 000000000..4389b6387 --- /dev/null +++ b/include/ezcomponents/Base/docs/repos/Me/myclass1.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/include/ezcomponents/Base/docs/repos/Me/myclass2.php b/include/ezcomponents/Base/docs/repos/Me/myclass2.php new file mode 100644 index 000000000..0e04f38b4 --- /dev/null +++ b/include/ezcomponents/Base/docs/repos/Me/myclass2.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/include/ezcomponents/Base/docs/repos/You/yourclass1.php b/include/ezcomponents/Base/docs/repos/You/yourclass1.php new file mode 100644 index 000000000..3a399a78f --- /dev/null +++ b/include/ezcomponents/Base/docs/repos/You/yourclass1.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/include/ezcomponents/Base/docs/repos/You/yourclass2.php b/include/ezcomponents/Base/docs/repos/You/yourclass2.php new file mode 100644 index 000000000..66cbf13e8 --- /dev/null +++ b/include/ezcomponents/Base/docs/repos/You/yourclass2.php @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/include/ezcomponents/Base/docs/repos/autoloads/my_autoload.php b/include/ezcomponents/Base/docs/repos/autoloads/my_autoload.php new file mode 100644 index 000000000..37e19e91b --- /dev/null +++ b/include/ezcomponents/Base/docs/repos/autoloads/my_autoload.php @@ -0,0 +1,6 @@ + 'Me/myclass1.php', + 'erMyClass2' => 'Me/myclass2.php', + ); +?> diff --git a/include/ezcomponents/Base/docs/repos/autoloads/your_autoload.php b/include/ezcomponents/Base/docs/repos/autoloads/your_autoload.php new file mode 100644 index 000000000..ceeeb4038 --- /dev/null +++ b/include/ezcomponents/Base/docs/repos/autoloads/your_autoload.php @@ -0,0 +1,6 @@ + 'You/yourclass1.php', + 'erYourClass2' => 'You/yourclass2.php', + ); +?> diff --git a/include/ezcomponents/Base/docs/tutorial.txt b/include/ezcomponents/Base/docs/tutorial.txt new file mode 100644 index 000000000..1bbc5d9fc --- /dev/null +++ b/include/ezcomponents/Base/docs/tutorial.txt @@ -0,0 +1,224 @@ +eZ Components - Base +~~~~~~~~~~~~~~~~~~~~ + +.. contents:: Table of Contents + +Introduction +============ + +The Base component provides the basic functionality, such as autoloading, that +all eZ Components need to function properly. The Base component needs to be +loaded specifically. Base can also autoload external class repositories from +outside the eZ Components. + +Aside from the autoload functionality, the Base component also contains a number of +generic Exception classes that all inherit from the ezcBaseException class. + + +Installation +============ + +The installation and configuration of the eZ Components environment is +described in a separate article. Please refer to the `Components Introduction`_ +for instructions on installation and configuration of the eZ Components library +and the Base component. + +.. _Components Introduction: /docs/install + + +Usage +===== + +Debugging +--------- + +By default the ezcBase component's autoload mechanism will not throw an +exception when an autoload class can not be found. In some cases (during +development) it is useful to have an exception with detailed information +about which autoload files where search for, and in which directories. +ezcBase supports an option that enables this behavior:: + + debug = true; + ezcBase::setOptions( $options ); + ?> + +**Warning**: Exceptions are ignored when they are thrown from an autoload() +handler in PHP. In order to see the exception message that is thrown when a +class can not be found, you need to catch the exception *in* the autoload() +handler. Your autoload() function could then look like:: + + function __autoload( $className ) + { + try + { + ezcBase::autoload( $className ); + } + catch ( Exception $e ) + { + echo $e->getMessage(); + } + } + +Preloading +---------- + +The default autoload policy of the eZ Components is to load every class +file on demand only. It is also possible to load all classes of one +component at the same time, when one of the component's classes is +requested for the first time. You can change this behavior with the +"preload" option that is available through the ezcBaseAutoloadOptions option +class. You can turn preloading on with:: + + preload = true; + ezcBase::setOptions( $options ); + ?> + +Please note that preloading will *not* be done for Exception classes. + +Adding class repositories located outside eZ Components to autoload system +-------------------------------------------------------------------------- + +It can be useful to add repositories of user-defined classes to the eZ +Components autoload system. The ezcBase::addClassRepository() method can be +used to perform this task. You need to arrange the desired external classes +in a class repository. That is, make sure that classes and corresponding +\*_autoload.php files are named and placed according to the explanations below. +After they are in the proper structure, you can call addClassRepository() with +the proper parameters before you use the external classes. +External classes will then be loaded by autoload system. + +ezcBase::addClassRepository() takes two arguments: + +- $basePath is the base path for the whole class repository. +- $autoloadDirPath is the path where autoload files for this repository are found. + +The paths in the autoload files are *not* relative to the package directory +as specified by the $basePath argument. In other words, class definition files will +only be searched for in the location $autoloadDirPath. + +Consider the following example: + +- There is a class repository stored in the directory "./repos". +- Autoload files for this repository are stored in "./repos/autoloads". +- There are two components in this repository: "Me" and "You". +- The "Me" component has the classes "erMyClass1" and "erMyClass2". +- The "You" component has the classes "erYourClass1" and "erYourClass2". + +In this case, you need to create the following files in "./repos/autoloads". +Note that the prefix to _autoload.php ("my" and "your") in the filename is the +first part of the classname (excluding the lowercase classname prefix - "er"). + +Content of my_autoload.php: + +.. include:: repos/autoloads/my_autoload.php + :literal: + +Content of your_autoload.php: + +.. include:: repos/autoloads/your_autoload.php + :literal: + +The directory structure for the external repository is then: :: + + ./repos/autoloads/my_autoload.php + ./repos/autoloads/your_autoload.php + ./repos/Me/myclass1.php + ./repos/Me/myclass2.php + ./repos/You/yourclass1.php + ./repos/You/yourclass2.php + +To use this repository with the autoload mechanism, use the +following code: + +.. include:: tutorial_example_01.php + :literal: + +The above code will output: :: + + Class 'erMyClass2' + Class 'erYourClass1' + +Lazy initialization +------------------- + +Lazy initialization is a mechanism to load and configure a component, only +when it is really used in your application. This mechanism saves time for +parsing the classes and configuration, when the component is not used at all +during one request. The implementation in ezcBaseInit may be reused by other +applications and components, like the following example will show. + +.. include:: tutorial_lazy_initialization.php + :literal: + +The example shows a random class implementing the singleton pattern, which may +be some database connection handler, or anything similar in your case. The +getInstance() method shows a typical PHP 5 implementation except the +additional line 14, which checks, if a configuration callback was provided +earlier and configures the newly created instance. If no configuration +callback was provided, nothing will happen. The customKey is used to receive +the right callback from ezcBaseInit and needs to be known by the user, who +wants to define a configuration callback for your class. + +In line 32 the class used to configure your instance on creation is defined. +The first parameter is the key used earlier in the getInstance method, to +reference the right class, and the second parameter is the name of your +configuration class. + +The configuration class beginning in line 22 just needs to implement the +ezcBaseConfigurationInitializer interface, which defines one +method: configureObject(). This method will be called with the object to +configure as a single parameter. In the example, a new public property on the +customSingleton instance is created, which will be echo'd later to show the +success of the configuration. + +The configuration itself will not happen before the actual instance is created +in line 35 performing the static call on customSingleton::getInstance(). The +var_dump() in the following line shows, that the property value is set and +contains the earlier set value (int) 42. + +File Operations +--------------- + +Finding files recursively +````````````````````````` + +This example shows how to use the ezcBaseFile::findRecursive() method: + +.. include:: tutorial_example_02.php + :literal: + +The code in this example searches for files in the ``/dat/dev/ezcomponents`` +directory. It will only include files that match *all* patterns in the +$includeFilters array (the second parameter). Files that match *any* of the +patterns in the $excludeFilters array (the third parameter) will not be returned. + +In other words, the code above searches for files in the ``dat/dev/ezcomponents`` +directory, which are in the ``src/`` directory and end with ``_autoload.php``, +except for files that are in the ``/autoload/`` directory. + +Removing directories recursively +```````````````````````````````` + +This example shows how to use the ezcBaseFile::removeRecursive() method: + +.. include:: tutorial_example_03.php + :literal: + +This code simply removes the directory ``/dat/dev/ezcomponents/trash`` and all +of its files and sub-directories. + +**Warning: Use this function with care, as it has the potential to erase +everything that the current user has access to.** + + + +.. + Local Variables: + mode: rst + fill-column: 79 + End: + vim: et syn=rst tw=79 diff --git a/include/ezcomponents/Base/docs/tutorial_autoload.php b/include/ezcomponents/Base/docs/tutorial_autoload.php new file mode 100644 index 000000000..66b1dcf1e --- /dev/null +++ b/include/ezcomponents/Base/docs/tutorial_autoload.php @@ -0,0 +1,20 @@ + diff --git a/include/ezcomponents/Base/docs/tutorial_example_01.php b/include/ezcomponents/Base/docs/tutorial_example_01.php new file mode 100644 index 000000000..316780d23 --- /dev/null +++ b/include/ezcomponents/Base/docs/tutorial_example_01.php @@ -0,0 +1,9 @@ +toString(); +$yourVar1 = new erYourClass1(); +$yourVar1->toString(); +?> diff --git a/include/ezcomponents/Base/docs/tutorial_example_02.php b/include/ezcomponents/Base/docs/tutorial_example_02.php new file mode 100644 index 000000000..3748b60cc --- /dev/null +++ b/include/ezcomponents/Base/docs/tutorial_example_02.php @@ -0,0 +1,11 @@ + diff --git a/include/ezcomponents/Base/docs/tutorial_example_03.php b/include/ezcomponents/Base/docs/tutorial_example_03.php new file mode 100644 index 000000000..3e989b253 --- /dev/null +++ b/include/ezcomponents/Base/docs/tutorial_example_03.php @@ -0,0 +1,6 @@ + diff --git a/include/ezcomponents/Base/docs/tutorial_lazy_initialization.php b/include/ezcomponents/Base/docs/tutorial_lazy_initialization.php new file mode 100644 index 000000000..2a6f5272d --- /dev/null +++ b/include/ezcomponents/Base/docs/tutorial_lazy_initialization.php @@ -0,0 +1,38 @@ +value = 42; + } +} + +// Register for lazy initilization +ezcBaseInit::setCallback( 'customKey', 'customSingletonConfiguration' ); + +// Configure on first initilization +$object = customSingleton::getInstance(); +var_dump( $object->value ); + +?> diff --git a/include/ezcomponents/Base/review-1.5.txt b/include/ezcomponents/Base/review-1.5.txt new file mode 100644 index 000000000..1ccfb4937 --- /dev/null +++ b/include/ezcomponents/Base/review-1.5.txt @@ -0,0 +1,23 @@ +Review Alexandru 2008-05-08 +=========================== + +[X] Regarding feature request #8529 (a du -s implementation). The documentation + for ezcBaseFile::findRecursive() says that you can supply an empty array + as the 4th argument to get the statistics. + + If I pass for example $stats which I initialized with array() before, then + I get notices: "Undefined index: count in /home/as/dev/ezcomponents/trunk/Base/src/file.php + on line 139", and the same notice for index "size". + + Also the documentation does not mention that you need to pass a variable and not + a value - if I pass array() as the 4th argument I get the error "Cannot pass + parameter 4 by reference" + + If I pass $stats which I initialize with null, false or empty string before, + then the function works. + + Also all the file recursive tests fail on Windows (slash issues mostly). + +[X] Regarding feature request #11506 (method ezcBase::getInstallationPath()). On + Linux it returns the path without any slash at the end, but on Windows (Vista) + it adds a Windows slash at the end. diff --git a/include/ezcomponents/Base/src/base.php b/include/ezcomponents/Base/src/base.php new file mode 100644 index 000000000..a203b79b9 --- /dev/null +++ b/include/ezcomponents/Base/src/base.php @@ -0,0 +1,592 @@ +array) + */ + protected static $repositoryDirs = array(); + + /** + * This variable stores all the elements from the autoload arrays. When a + * new autoload file is loaded, their files are added to this array. + * + * @var array(string=>string) + */ + protected static $autoloadArray = array(); + + /** + * This variable stores all the elements from the autoload arrays for + * external repositories. When a new autoload file is loaded, their files + * are added to this array. + * + * @var array(string=>string) + */ + protected static $externalAutoloadArray = array(); + + /** + * Options for the ezcBase class. + * + * @var ezcBaseOptions + */ + static private $options; + + /** + * Associates an option object with this static class. + * + * @param ezcBaseAutoloadOptions $options + */ + static public function setOptions( ezcBaseAutoloadOptions $options ) + { + self::$options = $options; + } + + /** + * Tries to autoload the given className. If the className could be found + * this method returns true, otherwise false. + * + * This class caches the requested class names (including the ones who + * failed to load). + * + * @param string $className The name of the class that should be loaded. + * + * @return bool + */ + public static function autoload( $className ) + { + ezcBase::setPackageDir(); + + // Check whether the classname is already in the cached autoloadArray. + if ( array_key_exists( $className, ezcBase::$autoloadArray ) ) + { + // Is it registered as 'unloadable'? + if ( ezcBase::$autoloadArray[$className] == false ) + { + return false; + } + ezcBase::loadFile( ezcBase::$autoloadArray[$className] ); + + return true; + } + + // Check whether the classname is already in the cached autoloadArray + // for external repositories. + if ( array_key_exists( $className, ezcBase::$externalAutoloadArray ) ) + { + // Is it registered as 'unloadable'? + if ( ezcBase::$externalAutoloadArray[$className] == false ) + { + return false; + } + ezcBase::loadExternalFile( ezcBase::$externalAutoloadArray[$className] ); + + return true; + } + + // Not cached, so load the autoload from the package. + // Matches the first and optionally the second 'word' from the classname. + $fileNames = array(); + if ( preg_match( "/^([a-z0-9]*)([A-Z][a-z0-9]*)([A-Z][a-z0-9]*)?/", $className, $matches ) !== false ) + { + $autoloadFile = ""; + // Try to match with both names, if available. + switch ( sizeof( $matches ) ) + { + case 4: + // check for x_y_autoload.php + $autoloadFile = strtolower( "{$matches[2]}_{$matches[3]}_autoload.php" ); + $fileNames[] = $autoloadFile; + if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) ) + { + return true; + } + // break intentionally missing. + + case 3: + // check for x_autoload.php + $autoloadFile = strtolower( "{$matches[2]}_autoload.php" ); + $fileNames[] = $autoloadFile; + if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) ) + { + return true; + } + + // check for autoload.php + $autoloadFile = 'autoload.php'; + $fileNames[] = $autoloadFile; + if ( ezcBase::requireFile( $autoloadFile, $className, $matches[1] ) ) + { + return true; + } + break; + } + + // Maybe there is another autoload available. + // Register this classname as false. + ezcBase::$autoloadArray[$className] = false; + } + + $path = ezcBase::$packageDir . 'autoload/'; + $realPath = realpath( $path ); + + if ( $realPath == '' ) + { + // Can not be tested, because if this happens, then the autoload + // environment has not been set-up correctly. + trigger_error( "Couldn't find autoload directory '$path'", E_USER_ERROR ); + } + + $dirs = self::getRepositoryDirectories(); + if ( ezcBase::$options && ezcBase::$options->debug ) + { + throw new ezcBaseAutoloadException( $className, $fileNames, $dirs ); + } + + return false; + } + + /** + * Sets the current working directory to $directory. + * + * @param string $directory + */ + public static function setWorkingDirectory( $directory ) + { + self::$libraryMode = 'custom'; + self::$currentWorkingDirectory = $directory; + } + + /** + * Figures out the base path of the eZ Components installation. + * + * It stores the path that it finds in a static member variable. The path + * depends on the installation method of the eZ Components. The SVN version + * has a different path than the PEAR installed version. + */ + protected static function setPackageDir() + { + if ( ezcBase::$packageDir !== null ) + { + return; + } + + // Get the path to the components. + $baseDir = dirname( __FILE__ ); + + switch ( ezcBase::$libraryMode ) + { + case "custom": + ezcBase::$packageDir = self::$currentWorkingDirectory . '/'; + break; + case "devel": + case "tarball": + ezcBase::$packageDir = $baseDir. "/../../"; + break; + case "pear"; + ezcBase::$packageDir = $baseDir. "/../"; + break; + } + } + + /** + * Tries to load the autoload array and, if loaded correctly, includes the class. + * + * @param string $fileName Name of the autoload file. + * @param string $className Name of the class that should be autoloaded. + * @param string $prefix The prefix of the class repository. + * + * @return bool True is returned when the file is correctly loaded. + * Otherwise false is returned. + */ + protected static function requireFile( $fileName, $className, $prefix ) + { + $autoloadDir = ezcBase::$packageDir . "autoload/"; + + // We need the full path to the fileName. The method file_exists() doesn't + // automatically check the (php.ini) library paths. Therefore: + // file_exists( "ezc/autoload/$fileName" ) doesn't work. + if ( $prefix === 'ezc' && file_exists( "$autoloadDir$fileName" ) ) + { + $array = require( "$autoloadDir$fileName" ); + + if ( is_array( $array) && array_key_exists( $className, $array ) ) + { + // Add the array to the cache, and include the requested file. + ezcBase::$autoloadArray = array_merge( ezcBase::$autoloadArray, $array ); + if ( ezcBase::$options !== null && ezcBase::$options->preload && !preg_match( '/Exception$/', $className ) ) + { + foreach ( $array as $loadClassName => $file ) + { + if ( $loadClassName !== 'ezcBase' && !class_exists( $loadClassName, false ) && !interface_exists( $loadClassName, false ) && !preg_match( '/Exception$/', $loadClassName ) /*&& !class_exists( $loadClassName, false ) && !interface_exists( $loadClassName, false )*/ ) + { + ezcBase::loadFile( ezcBase::$autoloadArray[$loadClassName] ); + } + } + } + else + { + ezcBase::loadFile( ezcBase::$autoloadArray[$className] ); + } + return true; + } + } + + // It is not in components autoload/ dir. + // try to search in additional dirs. + foreach ( ezcBase::$repositoryDirs as $repositoryPrefix => $extraDir ) + { + if ( gettype( $repositoryPrefix ) === 'string' && $repositoryPrefix !== $prefix ) + { + continue; + } + + if ( file_exists( $extraDir['autoloadDirPath'] . '/' . $fileName ) ) + { + $array = array(); + $originalArray = require( $extraDir['autoloadDirPath'] . '/' . $fileName ); + + // Building paths. + // Resulting path to class definition file consists of: + // path to extra directory with autoload file + + // basePath provided for current extra directory + + // path to class definition file stored in autoload file. + foreach ( $originalArray as $class => $classPath ) + { + $array[$class] = $extraDir['basePath'] . '/' . $classPath; + } + + if ( is_array( $array ) && array_key_exists( $className, $array ) ) + { + // Add the array to the cache, and include the requested file. + ezcBase::$externalAutoloadArray = array_merge( ezcBase::$externalAutoloadArray, $array ); + ezcBase::loadExternalFile( ezcBase::$externalAutoloadArray[$className] ); + return true; + } + } + } + + // Nothing found :-(. + return false; + } + + /** + * Loads, require(), the given file name. If we are in development mode, + * "/src/" is inserted into the path. + * + * @param string $file The name of the file that should be loaded. + */ + protected static function loadFile( $file ) + { + switch ( ezcBase::$libraryMode ) + { + case "devel": + case "tarball": + list( $first, $second ) = explode( '/', $file, 2 ); + $file = $first . "/src/" . $second; + break; + + case "custom": + list( $first, $second ) = explode( '/', $file, 2 ); + // Add the "src/" after the package name. + if ( $first == 'Base' || $first == 'UnitTest' ) + { + list( $first, $second ) = explode( '/', $file, 2 ); + $file = $first . "/src/" . $second; + } + else + { + list( $first, $second, $third ) = explode( '/', $file, 3 ); + $file = $first . '/' . $second . "/src/" . $third; + } + break; + + case "pear": + /* do nothing, it's already correct */ + break; + } + + if ( file_exists( ezcBase::$packageDir . $file ) ) + { + require( ezcBase::$packageDir . $file ); + } + else + { + // Can not be tested, because if this happens, then one of the + // components has a broken autoload file. + throw new ezcBaseFileNotFoundException( ezcBase::$packageDir.$file ); + } + } + + /** + * Loads, require(), the given file name from an external package. + * + * @param string $file The name of the file that should be loaded. + */ + protected static function loadExternalFile( $file ) + { + if ( file_exists( $file ) ) + { + require( $file ); + } + else + { + throw new ezcBaseFileNotFoundException( $file ); + } + } + + /** + * Checks for dependencies on PHP versions or extensions + * + * The function as called by the $component component checks for the $type + * dependency. The dependency $type is compared against the $value. The + * function aborts the script if the dependency is not matched. + * + * @param string $component + * @param int $type + * @param mixed $value + */ + public static function checkDependency( $component, $type, $value ) + { + switch ( $type ) + { + case self::DEP_PHP_EXTENSION: + if ( extension_loaded( $value ) ) + { + return; + } + else + { + // Can not be tested as it would abort the PHP script. + die( "\nThe {$component} component depends on the default PHP extension '{$value}', which is not loaded.\n" ); + } + break; + + case self::DEP_PHP_VERSION: + $phpVersion = phpversion(); + if ( version_compare( $phpVersion, $value, '>=' ) ) + { + return; + } + else + { + // Can not be tested as it would abort the PHP script. + die( "\nThe {$component} component depends on the PHP version '{$value}', but the current version is '{$phpVersion}'.\n" ); + } + break; + } + } + + /** + * Return the list of directories that contain class repositories. + * + * The path to the eZ components directory is always included in the result + * array. Each element in the returned array has the format of: + * packageDirectory => ezcBaseRepositoryDirectory + * + * @return array(string=>ezcBaseRepositoryDirectory) + */ + public static function getRepositoryDirectories() + { + $autoloadDirs = array(); + ezcBase::setPackageDir(); + $repositoryDir = self::$currentWorkingDirectory ? self::$currentWorkingDirectory : ( realpath( dirname( __FILE__ ) . '/../../' ) ); + $autoloadDirs['ezc'] = new ezcBaseRepositoryDirectory( ezcBaseRepositoryDirectory::TYPE_INTERNAL, $repositoryDir, $repositoryDir . "/autoload" ); + + foreach ( ezcBase::$repositoryDirs as $extraDirKey => $extraDirArray ) + { + $repositoryDirectory = new ezcBaseRepositoryDirectory( ezcBaseRepositoryDirectory::TYPE_EXTERNAL, realpath( $extraDirArray['basePath'] ), realpath( $extraDirArray['autoloadDirPath'] ) ); + $autoloadDirs[$extraDirKey] = $repositoryDirectory; + } + + return $autoloadDirs; + } + + /** + * Adds an additional class repository. + * + * Used for adding class repositoryies outside the eZ components to be + * loaded by the autoload system. + * + * This function takes two arguments: $basePath is the base path for the + * whole class repository and $autoloadDirPath the path where autoload + * files for this repository are found. The paths in the autoload files are + * relative to the package directory as specified by the $basePath + * argument. I.e. class definition file will be searched at location + * $basePath + path to the class definition file as stored in the autoload + * file. + * + * addClassRepository() should be called somewhere in code before external classes + * are used. + * + * Example: + * Take the following facts: + *
+ * 'Me/myclass1.php',
+ * 'erMyClass2' => 'Me/myclass2.php',
+ * );
+ * ?>
+ *
+ *
+ * "your_autoload.php":
+ *
+ * 'You/yourclass1.php',
+ * 'erYourClass2' => 'You/yourclass2.php',
+ * );
+ * ?>
+ *
+ *
+ * The directory structure for the external repository is then:
+ *
+ * ./repos/autoloads/my_autoload.php
+ * ./repos/autoloads/you_autoload.php
+ * ./repos/Me/myclass1.php
+ * ./repos/Me/myclass2.php
+ * ./repos/You/yourclass1.php
+ * ./repos/You/yourclass2.php
+ *
+ *
+ * To use this repository with the autoload mechanism you have to use the
+ * following code:
+ *
+ *
+ *
+ *
+ * @throws ezcBaseFileNotFoundException if $autoloadDirPath or $basePath do not exist.
+ * @param string $basePath
+ * @param string $autoloadDirPath
+ * @param string $prefix
+ */
+ public static function addClassRepository( $basePath, $autoloadDirPath = null, $prefix = null )
+ {
+ // check if base path exists
+ if ( !is_dir( $basePath ) )
+ {
+ throw new ezcBaseFileNotFoundException( $basePath, 'base directory' );
+ }
+
+ // calculate autoload path if it wasn't given
+ if ( is_null( $autoloadDirPath ) )
+ {
+ $autoloadDirPath = $basePath . '/autoload';
+ }
+
+ // check if autoload dir exists
+ if ( !is_dir( $autoloadDirPath ) )
+ {
+ throw new ezcBaseFileNotFoundException( $autoloadDirPath, 'autoload directory' );
+ }
+
+ // add info to $repositoryDirs
+ if ( $prefix === null )
+ {
+ $array = array( 'basePath' => $basePath, 'autoloadDirPath' => $autoloadDirPath );
+
+ // add info to the list of extra dirs
+ ezcBase::$repositoryDirs[] = $array;
+ }
+ else
+ {
+ if ( array_key_exists( $prefix, ezcBase::$repositoryDirs ) )
+ {
+ throw new ezcBaseDoubleClassRepositoryPrefixException( $prefix, $basePath, $autoloadDirPath );
+ }
+
+ // add info to the list of extra dirs, and use the prefix to identify the new repository.
+ ezcBase::$repositoryDirs[$prefix] = array( 'basePath' => $basePath, 'autoloadDirPath' => $autoloadDirPath );
+ }
+ }
+
+ /**
+ * Returns the base path of the eZ Components installation
+ *
+ * This method returns the base path, including a trailing directory
+ * separator.
+ *
+ * @return string
+ */
+ public static function getInstallationPath()
+ {
+ self::setPackageDir();
+
+ $path = realpath( self::$packageDir );
+ if ( substr( $path, -1 ) !== DIRECTORY_SEPARATOR )
+ {
+ $path .= DIRECTORY_SEPARATOR;
+ }
+ return $path;
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/exceptions/autoload.php b/include/ezcomponents/Base/src/exceptions/autoload.php
new file mode 100644
index 000000000..e822f80d3
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/autoload.php
@@ -0,0 +1,38 @@
+autoloadPath );
+ }
+ parent::__construct( "Could not find a class to file mapping for '{$className}'. Searched for ". implode( ', ', $files ) . " in: " . implode( ', ', $paths ) );
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/exceptions/double_class_repository_prefix.php b/include/ezcomponents/Base/src/exceptions/double_class_repository_prefix.php
new file mode 100644
index 000000000..d92064fc3
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/double_class_repository_prefix.php
@@ -0,0 +1,34 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/exception.php b/include/ezcomponents/Base/src/exceptions/exception.php
new file mode 100644
index 000000000..c5db8ac48
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/exception.php
@@ -0,0 +1,43 @@
+originalMessage = $message;
+
+ if ( php_sapi_name() == 'cli' )
+ {
+ parent::__construct( $message );
+ }
+ else
+ {
+ parent::__construct( htmlspecialchars( $message ) );
+ }
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/exceptions/extension_not_found.php b/include/ezcomponents/Base/src/exceptions/extension_not_found.php
new file mode 100644
index 000000000..4b21f780f
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/extension_not_found.php
@@ -0,0 +1,38 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/file_exception.php b/include/ezcomponents/Base/src/exceptions/file_exception.php
new file mode 100644
index 000000000..364b9a18f
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/file_exception.php
@@ -0,0 +1,25 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/file_io.php b/include/ezcomponents/Base/src/exceptions/file_io.php
new file mode 100644
index 000000000..c07e851ba
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/file_io.php
@@ -0,0 +1,50 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/file_not_found.php b/include/ezcomponents/Base/src/exceptions/file_not_found.php
new file mode 100644
index 000000000..d9de45004
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/file_not_found.php
@@ -0,0 +1,43 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/file_permission.php b/include/ezcomponents/Base/src/exceptions/file_permission.php
new file mode 100644
index 000000000..3cd2d090d
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/file_permission.php
@@ -0,0 +1,63 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/init_callback_configured.php b/include/ezcomponents/Base/src/exceptions/init_callback_configured.php
new file mode 100644
index 000000000..59f9c8f3f
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/init_callback_configured.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/invalid_callback_class.php b/include/ezcomponents/Base/src/exceptions/invalid_callback_class.php
new file mode 100644
index 000000000..450388c6b
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/invalid_callback_class.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/invalid_parent_class.php b/include/ezcomponents/Base/src/exceptions/invalid_parent_class.php
new file mode 100644
index 000000000..69d63055b
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/invalid_parent_class.php
@@ -0,0 +1,29 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/property_not_found.php b/include/ezcomponents/Base/src/exceptions/property_not_found.php
new file mode 100644
index 000000000..5c29bb79d
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/property_not_found.php
@@ -0,0 +1,30 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/property_permission.php b/include/ezcomponents/Base/src/exceptions/property_permission.php
new file mode 100644
index 000000000..e453c5ec0
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/property_permission.php
@@ -0,0 +1,42 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/setting_not_found.php b/include/ezcomponents/Base/src/exceptions/setting_not_found.php
new file mode 100644
index 000000000..2394b2c25
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/setting_not_found.php
@@ -0,0 +1,29 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/setting_value.php b/include/ezcomponents/Base/src/exceptions/setting_value.php
new file mode 100644
index 000000000..e72b3ef3c
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/setting_value.php
@@ -0,0 +1,42 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/value.php b/include/ezcomponents/Base/src/exceptions/value.php
new file mode 100644
index 000000000..c63e86b9d
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/value.php
@@ -0,0 +1,43 @@
+
diff --git a/include/ezcomponents/Base/src/exceptions/whatever.php b/include/ezcomponents/Base/src/exceptions/whatever.php
new file mode 100644
index 000000000..fd382249a
--- /dev/null
+++ b/include/ezcomponents/Base/src/exceptions/whatever.php
@@ -0,0 +1,40 @@
+
diff --git a/include/ezcomponents/Base/src/ezc_bootstrap.php b/include/ezcomponents/Base/src/ezc_bootstrap.php
new file mode 100644
index 000000000..cef136cfe
--- /dev/null
+++ b/include/ezcomponents/Base/src/ezc_bootstrap.php
@@ -0,0 +1,39 @@
+
diff --git a/include/ezcomponents/Base/src/features.php b/include/ezcomponents/Base/src/features.php
new file mode 100644
index 000000000..55d25f2a9
--- /dev/null
+++ b/include/ezcomponents/Base/src/features.php
@@ -0,0 +1,329 @@
+
+ *
+ *
+ *
+ * @package Base
+ * @version 1.5
+ */
+class ezcBaseFeatures
+{
+ /**
+ * Used to store the path of the ImageMagick convert utility.
+ *
+ * It is initialized in the {@link getImageConvertExecutable()} function.
+ *
+ * @var string
+ */
+ private static $imageConvert = null;
+
+ /**
+ * Used to store the path of the ImageMagick identify utility.
+ *
+ * It is initialized in the {@link getImageIdentifyExecutable()} function.
+ *
+ * @var string
+ */
+ private static $imageIdentify = null;
+
+ /**
+ * Used to store the operating system.
+ *
+ * It is initialized in the {@link os()} function.
+ *
+ * @var string
+ */
+ private static $os = null;
+
+ /**
+ * Determines if hardlinks are supported.
+ *
+ * @return bool
+ */
+ public static function supportsLink()
+ {
+ return function_exists( 'link' );
+ }
+
+ /**
+ * Determines if symlinks are supported.
+ *
+ * @return bool
+ */
+ public static function supportsSymLink()
+ {
+ return function_exists( 'symlink' );
+ }
+
+ /**
+ * Determines if posix uids are supported.
+ *
+ * @return bool
+ */
+ public static function supportsUserId()
+ {
+ return function_exists( 'posix_getpwuid' );
+ }
+
+ /**
+ * Determines if the ImageMagick convert utility is installed.
+ *
+ * @return bool
+ */
+ public static function hasImageConvert()
+ {
+ return !is_null( self::getImageConvertExecutable() );
+ }
+
+ /**
+ * Returns the path to the ImageMagick convert utility.
+ *
+ * On Linux, Unix,... it will return something like: /usr/bin/convert
+ * On Windows it will return something like: C:\Windows\System32\convert.exe
+ *
+ * @return string
+ */
+ public static function getImageConvertExecutable()
+ {
+ if ( !is_null( self::$imageConvert ) )
+ {
+ return self::$imageConvert;
+ }
+ return ( self::$imageConvert = self::findExecutableInPath( 'convert' ) );
+ }
+
+ /**
+ * Determines if the ImageMagick identify utility is installed.
+ *
+ * @return bool
+ */
+ public static function hasImageIdentify()
+ {
+ return !is_null( self::getImageIdentifyExecutable() );
+ }
+
+ /**
+ * Returns the path to the ImageMagick identify utility.
+ *
+ * On Linux, Unix,... it will return something like: /usr/bin/identify
+ * On Windows it will return something like: C:\Windows\System32\identify.exe
+ *
+ * @return string
+ */
+ public static function getImageIdentifyExecutable()
+ {
+ if ( !is_null( self::$imageIdentify ) )
+ {
+ return self::$imageIdentify;
+ }
+ return ( self::$imageIdentify = self::findExecutableInPath( 'identify' ) );
+ }
+
+ /**
+ * Determines if the specified extension is loaded.
+ *
+ * If $version is specified, the specified extension will be tested also
+ * against the version of the loaded extension.
+ *
+ * Examples:
+ *
+ * hasExtensionSupport( 'gzip' );
+ *
+ * will return true if gzip extension is loaded.
+ *
+ *
+ * hasExtensionSupport( 'pdo_mysql', '1.0.2' );
+ *
+ * will return true if pdo_mysql extension is loaded and its version is at least 1.0.2.
+ *
+ * @param string $extension
+ * @param string $version
+ * @return bool
+ */
+ public static function hasExtensionSupport( $extension, $version = null )
+ {
+ if ( is_null( $version ) )
+ {
+ return extension_loaded( $extension );
+ }
+ return extension_loaded( $extension ) && version_compare( phpversion( $extension ), $version, ">=" ) ;
+ }
+
+ /**
+ * Determines if the specified function is available.
+ *
+ * Examples:
+ *
+ * ezcBaseFeatures::hasFunction( 'imagepstext' );
+ *
+ * will return true if support for Type 1 fonts is available with your GD
+ * extension.
+ *
+ * @param string $functionName
+ * @return bool
+ */
+ public static function hasFunction( $functionName )
+ {
+ return function_exists( $functionName );
+ }
+
+ /**
+ * Returns if a given class exists.
+ * Checks for a given class name and returns if this class exists or not.
+ * Catches the ezcBaseAutoloadException and returns false, if it was thrown.
+ *
+ * @param string $className The class to check for.
+ * @param bool $autoload True to use __autoload(), otherwise false.
+ * @return bool True if the class exists. Otherwise false.
+ */
+ public static function classExists( $className, $autoload = true )
+ {
+ try
+ {
+ if ( class_exists( $className, $autoload ) )
+ {
+ return true;
+ }
+ return false;
+ }
+ catch ( ezcBaseAutoloadException $e )
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Returns the operating system on which PHP is running.
+ *
+ * This method returns a sanitized form of the OS name, example
+ * return values are "Windows", "Mac", "Linux" and "FreeBSD". In
+ * all other cases it returns the value of the internal PHP constant
+ * PHP_OS.
+ *
+ * @return string
+ */
+ public static function os()
+ {
+ if ( is_null( self::$os ) )
+ {
+ $uname = php_uname( 's' );
+ if ( substr( $uname, 0, 7 ) == 'Windows' )
+ {
+ self::$os = 'Windows';
+ }
+ elseif ( substr( $uname, 0, 3 ) == 'Mac' )
+ {
+ self::$os = 'Mac';
+ }
+ elseif ( strtolower( $uname ) == 'linux' )
+ {
+ self::$os = 'Linux';
+ }
+ elseif ( strtolower( substr( $uname, 0, 7 ) ) == 'freebsd' )
+ {
+ self::$os = 'FreeBSD';
+ }
+ else
+ {
+ self::$os = PHP_OS;
+ }
+ }
+ return self::$os;
+ }
+
+ /**
+ * Returns the path of the specified executable, if it can be found in the system's path.
+ *
+ * It scans the PATH enviroment variable based on the OS to find the
+ * $fileName. For Windows, the path is with \, not /. If $fileName is not
+ * found, it returns null.
+ *
+ * @todo consider using getenv( 'PATH' ) instead of $_ENV['PATH']
+ * (but that won't work under IIS)
+ *
+ * @param string $fileName
+ * @return string
+ */
+ public static function findExecutableInPath( $fileName )
+ {
+ if ( array_key_exists( 'PATH', $_ENV ) )
+ {
+ $envPath = $_ENV['PATH'];
+ if ( strlen( trim( $envPath ) ) == 0 )
+ {
+ $envPath = false;
+ }
+ }
+ else
+ {
+ $envPath = false;
+ }
+ switch ( self::os() )
+ {
+ case 'Unix':
+ case 'FreeBSD':
+ case 'Mac':
+ case 'MacOS':
+ case 'Darwin':
+ case 'Linux':
+ if ( $envPath )
+ {
+ $dirs = explode( ':', $envPath );
+ foreach ( $dirs as $dir )
+ {
+ if ( file_exists( "{$dir}/{$fileName}" ) )
+ {
+ return "{$dir}/{$fileName}";
+ }
+ }
+ }
+ elseif ( file_exists( "./{$fileName}" ) )
+ {
+ return $fileName;
+ }
+ break;
+ case 'Windows':
+ if ( $envPath )
+ {
+ $dirs = explode( ';', $envPath );
+ foreach ( $dirs as $dir )
+ {
+ if ( file_exists( "{$dir}\\{$fileName}.exe" ) )
+ {
+ return "{$dir}\\{$fileName}.exe";
+ }
+ }
+ }
+ elseif ( file_exists( "{$fileName}.exe" ) )
+ {
+ return "{$fileName}.exe";
+ }
+ break;
+ }
+ return null;
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/file.php b/include/ezcomponents/Base/src/file.php
new file mode 100644
index 000000000..2d4494cd9
--- /dev/null
+++ b/include/ezcomponents/Base/src/file.php
@@ -0,0 +1,377 @@
+
+ *
+ *
+ *
+ * @package Base
+ * @version 1.5
+ * @mainclass
+ */
+class ezcBaseFile
+{
+ /**
+ * Finds files recursively on a file system
+ *
+ * With this method you can scan the file system for files. You can use
+ * $includeFilters to include only specific files, and $excludeFilters to
+ * exclude certain files from being returned. The function will always go
+ * into subdirectories even if the entry would not have passed the filters.
+ *
+ * Filters are regular expressions and are therefore required to have
+ * starting and ending delimiters. The Perl Compatible syntax is used as
+ * regular expression language.
+ *
+ * If you pass an empty array to the $statistics argument, the function
+ * will in details about the number of files found into the 'count' array
+ * element, and the total filesize in the 'size' array element. Because this
+ * argument is passed by reference, you *have* to pass a variable and you
+ * can not pass a constant value such as "array()".
+ *
+ * @param string $sourceDir
+ * @param array(string) $includeFilters
+ * @param array(string) $excludeFilters
+ * @param array() $statistics
+ *
+ * @throws ezcBaseFileNotFoundException if the $sourceDir directory is not
+ * a directory or does not exist.
+ * @throws ezcBaseFilePermissionException if the $sourceDir directory could
+ * not be opened for reading.
+ * @return array
+ */
+ static public function findRecursive( $sourceDir, array $includeFilters = array(), array $excludeFilters = array(), &$statistics = null )
+ {
+ if ( !is_dir( $sourceDir ) )
+ {
+ throw new ezcBaseFileNotFoundException( $sourceDir, 'directory' );
+ }
+ $elements = array();
+ $d = @dir( $sourceDir );
+ if ( !$d )
+ {
+ throw new ezcBaseFilePermissionException( $sourceDir, ezcBaseFileException::READ );
+ }
+
+ // init statistics array
+ if ( !is_array( $statistics ) || !array_key_exists( 'size', $statistics ) || !array_key_exists( 'count', $statistics ) )
+ {
+ $statistics['size'] = 0;
+ $statistics['count'] = 0;
+ }
+
+ while ( ( $entry = $d->read() ) !== false )
+ {
+ if ( $entry == '.' || $entry == '..' )
+ {
+ continue;
+ }
+
+ $fileInfo = @stat( $sourceDir . DIRECTORY_SEPARATOR . $entry );
+ if ( !$fileInfo )
+ {
+ $fileInfo = array( 'size' => 0, 'mode' => 0 );
+ }
+
+ if ( $fileInfo['mode'] & 0x4000 )
+ {
+ // We need to ignore the Permission exceptions here as it can
+ // be normal that a directory can not be accessed. We only need
+ // the exception if the top directory could not be read.
+ try
+ {
+ $subList = self::findRecursive( $sourceDir . DIRECTORY_SEPARATOR . $entry, $includeFilters, $excludeFilters, $statistics );
+ $elements = array_merge( $elements, $subList );
+ }
+ catch ( ezcBaseFilePermissionException $e )
+ {
+ }
+ }
+ else
+ {
+ // By default a file is included in the return list
+ $ok = true;
+ // Iterate over the $includeFilters and prohibit the file from
+ // being returned when atleast one of them does not match
+ foreach ( $includeFilters as $filter )
+ {
+ if ( !preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR . $entry ) )
+ {
+ $ok = false;
+ break;
+ }
+ }
+ // Iterate over the $excludeFilters and prohibit the file from
+ // being returns when atleast one of them matches
+ foreach ( $excludeFilters as $filter )
+ {
+ if ( preg_match( $filter, $sourceDir . DIRECTORY_SEPARATOR . $entry ) )
+ {
+ $ok = false;
+ break;
+ }
+ }
+
+ if ( $ok )
+ {
+ $elements[] = $sourceDir . DIRECTORY_SEPARATOR . $entry;
+ $statistics['count']++;
+ $statistics['size'] += $fileInfo['size'];
+ }
+ }
+ }
+ sort( $elements );
+ return $elements;
+ }
+
+ /**
+ * Removes files and directories recursively from a file system
+ *
+ * This method recursively removes the $directory and all its contents.
+ * You should be extremely careful with this method as it has the
+ * potential to erase everything that the current user has access to.
+ *
+ * @param string $directory
+ */
+ static public function removeRecursive( $directory )
+ {
+ $sourceDir = realpath( $directory );
+ if ( !$sourceDir )
+ {
+ throw new ezcBaseFileNotFoundException( $directory, 'directory' );
+ }
+ $d = @dir( $sourceDir );
+ if ( !$d )
+ {
+ throw new ezcBaseFilePermissionException( $directory, ezcBaseFileException::READ );
+ }
+ while ( ( $entry = $d->read() ) !== false )
+ {
+ if ( $entry == '.' || $entry == '..' )
+ {
+ continue;
+ }
+
+ if ( is_dir( $sourceDir . DIRECTORY_SEPARATOR . $entry ) )
+ {
+ self::removeRecursive( $sourceDir . DIRECTORY_SEPARATOR . $entry );
+ }
+ else
+ {
+ if ( @unlink( $sourceDir . DIRECTORY_SEPARATOR . $entry ) === false )
+ {
+ throw new ezcBaseFilePermissionException( $directory . DIRECTORY_SEPARATOR . $entry, ezcBaseFileException::REMOVE );
+ }
+ }
+ }
+ $d->close();
+ rmdir( $sourceDir );
+ }
+
+ /**
+ * Recursively copy a file or directory.
+ *
+ * Recursively copy a file or directory in $source to the given
+ * destination. If a depth is given, the operation will stop, if the given
+ * recursion depth is reached. A depth of -1 means no limit, while a depth
+ * of 0 means, that only the current file or directory will be copied,
+ * without any recursion.
+ *
+ * You may optionally define modes used to create files and directories.
+ *
+ * @throws ezcBaseFileNotFoundException
+ * If the $sourceDir directory is not a directory or does not exist.
+ * @throws ezcBaseFilePermissionException
+ * If the $sourceDir directory could not be opened for reading, or the
+ * destination is not writeable.
+ *
+ * @param string $source
+ * @param string $destination
+ * @param int $depth
+ * @param int $dirMode
+ * @param int $fileMode
+ * @return void
+ */
+ static public function copyRecursive( $source, $destination, $depth = -1, $dirMode = 0775, $fileMode = 0664 )
+ {
+ // Check if source file exists at all.
+ if ( !is_file( $source ) && !is_dir( $source ) )
+ {
+ throw new ezcBaseFileNotFoundException( $source );
+ }
+
+ // Destination file should NOT exist
+ if ( is_file( $destination ) || is_dir( $destination ) )
+ {
+ throw new ezcBaseFilePermissionException( $destination, ezcBaseFileException::WRITE );
+ }
+
+ // Skip non readable files in source directory
+ if ( !is_readable( $source ) )
+ {
+ return;
+ }
+
+ // Copy
+ if ( is_dir( $source ) )
+ {
+ mkdir( $destination );
+ // To ignore umask, umask() should not be changed with
+ // multithreaded servers...
+ chmod( $destination, $dirMode );
+ }
+ elseif ( is_file( $source ) )
+ {
+ copy( $source, $destination );
+ chmod( $destination, $fileMode );
+ }
+
+ if ( ( $depth === 0 ) ||
+ ( !is_dir( $source ) ) )
+ {
+ // Do not recurse (any more)
+ return;
+ }
+
+ // Recurse
+ $dh = opendir( $source );
+ while( $file = readdir( $dh ) )
+ {
+ if ( ( $file === '.' ) ||
+ ( $file === '..' ) )
+ {
+ continue;
+ }
+
+ self::copyRecursive(
+ $source . '/' . $file,
+ $destination . '/' . $file,
+ $depth - 1, $dirMode, $fileMode
+ );
+ }
+ }
+
+ /**
+ * Calculates the relative path of the file/directory '$path' to a given
+ * $base path.
+ * This method does not touch the filesystem.
+ *
+ * @param string $path
+ * @param string $base
+ * @return string
+ */
+ static public function calculateRelativePath( $path, $base )
+ {
+ // Sanitize the paths to use the correct directory separator for the platform
+ $path = strtr( $path, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR );
+ $base = strtr( $base, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR );
+
+ $base = explode( DIRECTORY_SEPARATOR, $base );
+ $path = explode( DIRECTORY_SEPARATOR, $path );
+
+ $result = '';
+
+ $pathPart = array_shift( $path );
+ $basePart = array_shift( $base );
+ while ( $pathPart == $basePart )
+ {
+ $pathPart = array_shift( $path );
+ $basePart = array_shift( $base );
+ }
+
+ if ( $pathPart != null )
+ {
+ array_unshift( $path, $pathPart );
+ }
+ if ( $basePart != null )
+ {
+ array_unshift( $base, $basePart );
+ }
+
+ $result = str_repeat( '..' . DIRECTORY_SEPARATOR, count( $base ) );
+ $result .= join( DIRECTORY_SEPARATOR, $path );
+
+ return $result;
+ }
+
+ /**
+ * Returns whether the passed $path is an absolute path, giving the current $os.
+ *
+ * With the $os parameter you can tell this function to use the semantics
+ * for a different operating system to determine whether a path is
+ * absolute. The $os argument defaults to the OS that the script is running
+ * on.
+ *
+ * @param string $path
+ * @param string $os
+ * @return bool
+ */
+ public static function isAbsolutePath( $path, $os = null )
+ {
+ if ( $os === null )
+ {
+ $os = ezcBaseFeatures::os();
+ }
+
+ switch ( $os )
+ {
+ case 'Windows':
+ // Sanitize the paths to use the correct directory separator for the platform
+ $path = strtr( $path, '\\/', '\\\\' );
+
+ // Absolute paths with drive letter: X:\
+ if ( preg_match( '@^[A-Z]:\\\\@i', $path ) )
+ {
+ return true;
+ }
+
+ // Absolute paths with network paths: \\server\share\
+ if ( preg_match( '@^\\\\\\\\[A-Z]+\\\\[^\\\\]@i', $path ) )
+ {
+ return true;
+ }
+ break;
+ case 'Mac':
+ case 'Linux':
+ case 'FreeBSD':
+ default:
+ // Sanitize the paths to use the correct directory separator for the platform
+ $path = strtr( $path, '\\/', '//' );
+
+ if ( $path[0] == '/' )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/init.php b/include/ezcomponents/Base/src/init.php
new file mode 100644
index 000000000..805ca947c
--- /dev/null
+++ b/include/ezcomponents/Base/src/init.php
@@ -0,0 +1,125 @@
+
+ *
+ *
+ *
+ * You will also need to configure which callback class to call. This you do
+ * with the ezcBaseInit::setCallback() method. The following examples sets the
+ * callback classname for the configuration identifier
+ * 'ezcInitConfigurationManager' to 'cfgConfigurationManager':
+ *
+ *
+ *
+ *
+ *
+ * The class 'cfgConfigurationManager' is required to implement the
+ * ezcBaseConfigurationInitializer interface, which defines only one method:
+ * configureObject(). An example on how to implement such a class could be:
+ *
+ *
+ * init( 'ezcConfigurationIniReader', 'settings', array( 'useComments' => true ) );
+ * }
+ * }
+ * ?>
+ *
+ *
+ * Of course the implementation of this callback class is up to the application
+ * developer that uses the component (in this example the Configuration
+ * component's class ezcConfigurationManager).
+ *
+ * @package Base
+ * @version 1.5
+ */
+class ezcBaseInit
+{
+ /**
+ * Contains the callback where the identifier is the key of the array, and the classname to callback to the value.
+ *
+ * @var array(string=>string)
+ */
+ static private $callbackMap = array();
+
+ /**
+ * Adds the classname $callbackClassname as callback for the identifier $identifier.
+ *
+ * @param string $identifier
+ * @param string $callbackClassname
+ */
+ public static function setCallback( $identifier, $callbackClassname )
+ {
+ if ( array_key_exists( $identifier, self::$callbackMap ) )
+ {
+ throw new ezcBaseInitCallbackConfiguredException( $identifier, self::$callbackMap[$identifier] );
+ }
+ else
+ {
+ // Check if the passed classname actually exists
+ if ( !ezcBaseFeatures::classExists( $callbackClassname, true ) )
+ {
+ throw new ezcBaseInitInvalidCallbackClassException( $callbackClassname );
+ }
+
+ // Check if the passed classname actually implements the interface.
+ if ( !in_array( 'ezcBaseConfigurationInitializer', class_implements( $callbackClassname ) ) )
+ {
+ throw new ezcBaseInitInvalidCallbackClassException( $callbackClassname );
+ }
+
+ self::$callbackMap[$identifier] = $callbackClassname;
+ }
+ }
+
+ /**
+ * Uses the configured callback belonging to $identifier to configure the $object.
+ *
+ * The method will return the return value of the callback method, or null
+ * in case there was no callback set for the specified $identifier.
+ *
+ * @param string $identifier
+ * @param object $object
+ * @return mixed
+ */
+ public static function fetchConfig( $identifier, $object )
+ {
+ if ( isset( self::$callbackMap[$identifier] ) )
+ {
+ $callbackClassname = self::$callbackMap[$identifier];
+ return call_user_func( array( $callbackClassname, 'configureObject' ), $object );
+ }
+ return null;
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/interfaces/configuration_initializer.php b/include/ezcomponents/Base/src/interfaces/configuration_initializer.php
new file mode 100644
index 000000000..e7d5d13d7
--- /dev/null
+++ b/include/ezcomponents/Base/src/interfaces/configuration_initializer.php
@@ -0,0 +1,30 @@
+
diff --git a/include/ezcomponents/Base/src/interfaces/persistable.php b/include/ezcomponents/Base/src/interfaces/persistable.php
new file mode 100644
index 000000000..b46c04e73
--- /dev/null
+++ b/include/ezcomponents/Base/src/interfaces/persistable.php
@@ -0,0 +1,40 @@
+mixed)
+ */
+ public function getState();
+
+ /**
+ * Accepts an array containing data for one or more of the class' properties.
+ *
+ * @param array $properties
+ */
+ public function setState( array $properties );
+}
+?>
diff --git a/include/ezcomponents/Base/src/options.php b/include/ezcomponents/Base/src/options.php
new file mode 100644
index 000000000..baeda2da1
--- /dev/null
+++ b/include/ezcomponents/Base/src/options.php
@@ -0,0 +1,174 @@
+mixed)
+ */
+ protected $properties;
+
+ /**
+ * Construct a new options object.
+ * Options are constructed from an option array by default. The constructor
+ * automatically passes the given options to the __set() method to set them
+ * in the class.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If trying to access a non existent property.
+ * @throws ezcBaseValueException
+ * If the value for a property is out of range.
+ * @param array(string=>mixed) $options The initial options to set.
+ */
+ public function __construct( array $options = array() )
+ {
+ foreach ( $options as $option => $value )
+ {
+ $this->__set( $option, $value );
+ }
+ }
+
+ /**
+ * Merge an array into the actual options object.
+ * This method merges an array of new options into the actual options object.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If trying to access a non existent property.
+ * @throws ezcBaseValueException
+ * If the value for a property is out of range.
+ * @param array(string=>mixed) $newOptions The new options.
+ */
+ public function merge( array $newOptions )
+ {
+ foreach ( $newOptions as $key => $value )
+ {
+ $this->__set( $key, $value );
+ }
+ }
+
+ /**
+ * Property get access.
+ * Simply returns a given option.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @param string $propertyName The name of the option to get.
+ * @return mixed The option value.
+ * @ignore
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * if the given property does not exist.
+ * @throws ezcBasePropertyPermissionException
+ * if the property to be set is a write-only property.
+ */
+ public function __get( $propertyName )
+ {
+ if ( $this->__isset( $propertyName ) === true )
+ {
+ return $this->properties[$propertyName];
+ }
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ }
+
+ /**
+ * Sets an option.
+ * This method is called when an option is set.
+ *
+ * @param string $propertyName The name of the option to set.
+ * @param mixed $propertyValue The option value.
+ * @ignore
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * if the given property does not exist.
+ * @throws ezcBaseValueException
+ * if the value to be assigned to a property is invalid.
+ * @throws ezcBasePropertyPermissionException
+ * if the property to be set is a read-only property.
+ */
+ abstract public function __set( $propertyName, $propertyValue );
+
+ /**
+ * Returns if a option exists.
+ *
+ * @param string $propertyName Option name to check for.
+ * @return bool Whether the option exists.
+ * @ignore
+ */
+ public function __isset( $propertyName )
+ {
+ return array_key_exists( $propertyName, $this->properties );
+ }
+
+ /**
+ * Returns if an option exists.
+ * Allows isset() using ArrayAccess.
+ *
+ * @param string $propertyName The name of the option to get.
+ * @return bool Whether the option exists.
+ */
+ public function offsetExists( $propertyName )
+ {
+ return $this->__isset( $propertyName );
+ }
+
+ /**
+ * Returns an option value.
+ * Get an option value by ArrayAccess.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If $propertyName is not a key in the $properties array.
+ * @param string $propertyName The name of the option to get.
+ * @return mixed The option value.
+ */
+ public function offsetGet( $propertyName )
+ {
+ return $this->__get( $propertyName );
+ }
+
+ /**
+ * Set an option.
+ * Sets an option using ArrayAccess.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If $propertyName is not a key in the $properties array.
+ * @throws ezcBaseValueException
+ * If the value for a property is out of range.
+ * @param string $propertyName The name of the option to set.
+ * @param mixed $propertyValue The value for the option.
+ */
+ public function offsetSet( $propertyName, $propertyValue )
+ {
+ $this->__set( $propertyName, $propertyValue );
+ }
+
+ /**
+ * Unset an option.
+ * Unsets an option using ArrayAccess.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If $propertyName is not a key in the $properties array.
+ * @throws ezcBaseValueException
+ * If a the value for a property is out of range.
+ * @param string $propertyName The name of the option to unset.
+ */
+ public function offsetUnset( $propertyName )
+ {
+ $this->__set( $propertyName, null );
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/options/autoload.php b/include/ezcomponents/Base/src/options/autoload.php
new file mode 100644
index 000000000..e6faec3f8
--- /dev/null
+++ b/include/ezcomponents/Base/src/options/autoload.php
@@ -0,0 +1,75 @@
+mixed) $options
+ */
+ public function __construct( array $options = array() )
+ {
+ $this->preload = false;
+ $this->debug = false;
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * Sets the option $name to $value.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * if the property $name is not defined
+ * @throws ezcBaseValueException
+ * if $value is not correct for the property $name
+ * @param string $name
+ * @param mixed $value
+ * @ignore
+ */
+ public function __set( $name, $value )
+ {
+ switch ( $name )
+ {
+ case 'debug':
+ case 'preload':
+ if ( !is_bool( $value ) )
+ {
+ throw new ezcBaseValueException( $name, $value, 'bool' );
+ }
+ $this->properties[$name] = $value;
+ break;
+
+ default:
+ throw new ezcBasePropertyNotFoundException( $name );
+ }
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/src/struct.php b/include/ezcomponents/Base/src/struct.php
new file mode 100644
index 000000000..5a36a1ee9
--- /dev/null
+++ b/include/ezcomponents/Base/src/struct.php
@@ -0,0 +1,42 @@
+
diff --git a/include/ezcomponents/Base/src/structs/repository_directory.php b/include/ezcomponents/Base/src/structs/repository_directory.php
new file mode 100644
index 000000000..fdcf0622a
--- /dev/null
+++ b/include/ezcomponents/Base/src/structs/repository_directory.php
@@ -0,0 +1,83 @@
+type = $type;
+ $this->basePath = $basePath;
+ $this->autoloadPath = $autoloadPath;
+ }
+
+ /**
+ * Returns a new instance of this class with the data specified by $array.
+ *
+ * $array contains all the data members of this class in the form:
+ * array('member_name'=>value).
+ *
+ * __set_state makes this class exportable with var_export.
+ * var_export() generates code, that calls this method when it
+ * is parsed with PHP.
+ *
+ * @param array(string=>mixed) $array
+ * @return ezcBaseRepositoryDirectory
+ */
+ static public function __set_state( array $array )
+ {
+ return new ezcBaseRepositoryDirectory( $array['type'], $array['basePath'], $array['autoloadPath'] );
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/base_init_test.php b/include/ezcomponents/Base/tests/base_init_test.php
new file mode 100644
index 000000000..7a6298e79
--- /dev/null
+++ b/include/ezcomponents/Base/tests/base_init_test.php
@@ -0,0 +1,81 @@
+fail( "Expected exception not thrown." );
+ }
+ catch ( ezcBaseInitInvalidCallbackClassException $e )
+ {
+ $this->assertEquals( "Class 'classDoesNotExist' does not exist, or does not implement the 'ezcBaseConfigurationInitializer' interface.", $e->getMessage() );
+ }
+ }
+
+ public function testCallbackWithClassThatDoesNotImplementTheInterface()
+ {
+ try
+ {
+ ezcBaseInit::setCallback( 'testBaseInit', 'ezcBaseFeatures' );
+ $this->fail( "Expected exception not thrown." );
+ }
+ catch ( ezcBaseInitInvalidCallbackClassException $e )
+ {
+ $this->assertEquals( "Class 'ezcBaseFeatures' does not exist, or does not implement the 'ezcBaseConfigurationInitializer' interface.", $e->getMessage() );
+ }
+ }
+
+ public function testCallback1()
+ {
+ $obj = testBaseInitClass::getInstance();
+ $this->assertEquals( false, $obj->configured );
+ }
+
+ public function testCallback2()
+ {
+ ezcBaseInit::setCallback( 'testBaseInit', 'testBaseInitCallback' );
+ $obj = testBaseInitClass::getInstance();
+ $this->assertEquals( true, $obj->configured );
+ }
+
+ public function testCallback3()
+ {
+ try
+ {
+ ezcBaseInit::setCallback( 'testBaseInit', 'testBaseInitCallback' );
+ $this->fail( "Expected exception not thrown." );
+ }
+ catch ( ezcBaseInitCallbackConfiguredException $e )
+ {
+ $this->assertEquals( "The 'testBaseInit' is already configured with callback class 'testBaseInitCallback'.", $e->getMessage() );
+ }
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite("ezcBaseInitTest");
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/base_options_test.php b/include/ezcomponents/Base/tests/base_options_test.php
new file mode 100644
index 000000000..9edb30caa
--- /dev/null
+++ b/include/ezcomponents/Base/tests/base_options_test.php
@@ -0,0 +1,129 @@
+properties;
+ }
+ catch ( ezcBasePropertyNotFoundException $e )
+ {
+ return;
+ }
+ $this->fail( "ezcBasePropertyNotFoundException not thrown on access to forbidden property \$properties" );
+ }
+
+ public function testGetOffsetAccessFailure()
+ {
+ $opt = new ezcBaseTestOptions();
+ try
+ {
+ echo $opt["properties"];
+ }
+ catch ( ezcBasePropertyNotFoundException $e )
+ {
+ return;
+ }
+ $this->fail( "ezcBasePropertyNotFoundException not thrown on access to forbidden property \$properties" );
+ }
+
+ public function testSetOffsetAccessFailure()
+ {
+ $opt = new ezcBaseTestOptions();
+ try
+ {
+ $opt["properties"] = "foo";
+ }
+ catch ( ezcBasePropertyNotFoundException $e )
+ {
+ return;
+ }
+ $this->fail( "ezcBasePropertyNotFoundException not thrown on access to forbidden property \$properties" );
+ }
+
+ public function testConstructorWithParameters()
+ {
+ $options = new ezcBaseTestOptions( array( 'foo' => 'xxx' ) );
+ $this->assertEquals( 'xxx', $options->foo );
+ }
+
+ public function testMerge()
+ {
+ $options = new ezcBaseTestOptions();
+ $this->assertEquals( 'bar', $options->foo );
+ $options->merge( array( 'foo' => 'xxx' ) );
+ $this->assertEquals( 'xxx', $options->foo );
+ }
+
+ public function testOffsetExists()
+ {
+ $options = new ezcBaseTestOptions();
+ $this->assertEquals( true, $options->offsetExists( 'foo' ) );
+ $this->assertEquals( false, $options->offsetExists( 'bar' ) );
+ }
+
+ public function testOffsetSet()
+ {
+ $options = new ezcBaseTestOptions();
+ $this->assertEquals( 'bar', $options->foo );
+ $options->offsetSet( 'foo', 'xxx' );
+ $this->assertEquals( 'xxx', $options->foo );
+ }
+
+ public function testOffsetUnset()
+ {
+ $options = new ezcBaseTestOptions();
+ $this->assertEquals( 'bar', $options->foo );
+ $options->offsetUnset( 'foo' );
+ $this->assertEquals( null, $options->foo );
+ $this->assertEquals( true, $options->offsetExists( 'foo' ) );
+ }
+
+ public function testAutoloadOptions()
+ {
+ $options = new ezcBaseAutoloadOptions();
+
+ try
+ {
+ $options->no_such_property = 'value';
+ $this->fail( 'Expected exception was not thrown.' );
+ }
+ catch ( ezcBasePropertyNotFoundException $e )
+ {
+ $this->assertEquals( "No such property name 'no_such_property'.", $e->getMessage() );
+ }
+
+ try
+ {
+ $options->preload = 'wrong value';
+ $this->fail( 'Expected exception was not thrown.' );
+ }
+ catch ( ezcBaseValueException $e )
+ {
+ $this->assertEquals( "The value 'wrong value' that you were trying to assign to setting 'preload' is invalid. Allowed values are: bool.", $e->getMessage() );
+ }
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Base/tests/base_test.php b/include/ezcomponents/Base/tests/base_test.php
new file mode 100644
index 000000000..fdb3c738d
--- /dev/null
+++ b/include/ezcomponents/Base/tests/base_test.php
@@ -0,0 +1,497 @@
+assertEquals( "The setting 'broken' is not a valid configuration setting.", $e->getMessage() );
+ }
+ }
+
+ public function testConfigExceptionOutOfRange1()
+ {
+ try
+ {
+ throw new ezcBaseSettingValueException( 'broken', 42 );
+ }
+ catch ( ezcBaseSettingValueException $e )
+ {
+ $this->assertEquals( "The value '42' that you were trying to assign to setting 'broken' is invalid.", $e->getMessage() );
+ }
+ }
+
+ public function testConfigExceptionOutOfRange2()
+ {
+ try
+ {
+ throw new ezcBaseSettingValueException( 'broken', 42, "int, 40 - 48" );
+ }
+ catch ( ezcBaseSettingValueException $e )
+ {
+ $this->assertEquals( "The value '42' that you were trying to assign to setting 'broken' is invalid. Allowed values are: int, 40 - 48", $e->getMessage() );
+ }
+ }
+
+ public function testConfigExceptionOutOfRange3()
+ {
+ try
+ {
+ throw new ezcBaseSettingValueException( 'broken', array(1, 1, 3, 4, 5), 'int' );
+ }
+ catch ( ezcBaseSettingValueException $e )
+ {
+ $this->assertEquals( "The value 'a:5:{i:0;i:1;i:1;i:1;i:2;i:3;i:3;i:4;i:4;i:5;}' that you were trying to assign to setting 'broken' is invalid. Allowed values are: int", $e->getMessage() );
+ }
+ }
+
+ public function testFileIoException1()
+ {
+ try
+ {
+ throw new ezcBaseFileIoException( 'testfile.php', ezcBaseFileException::READ );
+ }
+ catch ( ezcBaseFileIoException $e )
+ {
+ $this->assertEquals( "An error occurred while reading from 'testfile.php'.", $e->getMessage() );
+ }
+ }
+
+ public function testFileIoException2()
+ {
+ try
+ {
+ throw new ezcBaseFileIoException( 'testfile.php', ezcBaseFileException::WRITE );
+ }
+ catch ( ezcBaseFileIoException $e )
+ {
+ $this->assertEquals( "An error occurred while writing to 'testfile.php'.", $e->getMessage() );
+ }
+ }
+
+ public function testFileIoException3()
+ {
+ try
+ {
+ throw new ezcBaseFileIoException( 'testfile.php', ezcBaseFileException::WRITE, "Extra extra" );
+ }
+ catch ( ezcBaseFileIoException $e )
+ {
+ $this->assertEquals( "An error occurred while writing to 'testfile.php'. (Extra extra)", $e->getMessage() );
+ }
+ }
+
+ public function testFileNotFoundException1()
+ {
+ try
+ {
+ throw new ezcBaseFileNotFoundException( 'testfile.php' );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ $this->assertEquals( "The file 'testfile.php' could not be found.", $e->getMessage() );
+ }
+ }
+
+ public function testFileNotFoundException2()
+ {
+ try
+ {
+ throw new ezcBaseFileNotFoundException( 'testfile.php', 'INI' );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ $this->assertEquals( "The INI file 'testfile.php' could not be found.", $e->getMessage() );
+ }
+ }
+
+ public function testFileNotFoundException3()
+ {
+ try
+ {
+ throw new ezcBaseFileNotFoundException( 'testfile.php', 'INI', "Extra extra" );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ $this->assertEquals( "The INI file 'testfile.php' could not be found. (Extra extra)", $e->getMessage() );
+ }
+ }
+
+ public function testFilePermissionException1()
+ {
+ try
+ {
+ throw new ezcBaseFilePermissionException( 'testfile.php', ezcBaseFileException::READ );
+ }
+ catch ( ezcBaseFilePermissionException $e )
+ {
+ $this->assertEquals( "The file 'testfile.php' can not be opened for reading.", $e->getMessage() );
+ }
+ }
+
+ public function testFilePermissionException2()
+ {
+ try
+ {
+ throw new ezcBaseFilePermissionException( 'testfile.php', ezcBaseFileException::WRITE );
+ }
+ catch ( ezcBaseFileException $e )
+ {
+ $this->assertEquals( "The file 'testfile.php' can not be opened for writing.", $e->getMessage() );
+ }
+ }
+
+ public function testFilePermissionException3()
+ {
+ try
+ {
+ throw new ezcBaseFilePermissionException( 'testfile.php', ezcBaseFileException::EXECUTE );
+ }
+ catch ( ezcBaseException $e )
+ {
+ $this->assertEquals( "The file 'testfile.php' can not be executed.", $e->getMessage() );
+ }
+ }
+
+ public function testFilePermissionException4()
+ {
+ try
+ {
+ throw new ezcBaseFilePermissionException( 'testfile.php', ezcBaseFilePermissionException::CHANGE, "Extra extra" );
+ }
+ catch ( ezcBaseException $e )
+ {
+ $this->assertEquals( "The permissions for 'testfile.php' can not be changed. (Extra extra)", $e->getMessage() );
+ }
+ }
+
+ public function testFilePermissionException5()
+ {
+ try
+ {
+ throw new ezcBaseFilePermissionException( 'testfile.php', ezcBaseFilePermissionException::READ | ezcBaseFilePermissionException::WRITE, "Extra extra" );
+ }
+ catch ( ezcBaseException $e )
+ {
+ $this->assertEquals( "The file 'testfile.php' can not be opened for reading and writing. (Extra extra)", $e->getMessage() );
+ }
+ }
+
+ public function testFilePermissionException6()
+ {
+ try
+ {
+ throw new ezcBaseFilePermissionException( 'testfile.php', ezcBaseFilePermissionException::REMOVE, "Extra extra" );
+ }
+ catch ( ezcBaseException $e )
+ {
+ $this->assertEquals( "The file 'testfile.php' can not be removed. (Extra extra)", $e->getMessage() );
+ }
+ }
+
+ public function testPropertyNotFoundException()
+ {
+ try
+ {
+ throw new ezcBasePropertyNotFoundException( 'broken' );
+ }
+ catch ( ezcBasePropertyNotFoundException $e )
+ {
+ $this->assertEquals( "No such property name 'broken'.", $e->getMessage() );
+ }
+ }
+
+ public function testPropertyPermissionException1()
+ {
+ try
+ {
+ throw new ezcBasePropertyPermissionException( 'broken', ezcBasePropertyPermissionException::READ );
+ }
+ catch ( ezcBaseException $e )
+ {
+ $this->assertEquals( "The property 'broken' is read-only.", $e->getMessage() );
+ }
+ }
+
+ public function testPropertyPermissionException2()
+ {
+ try
+ {
+ throw new ezcBasePropertyPermissionException( 'broken', ezcBasePropertyPermissionException::WRITE );
+ }
+ catch ( ezcBaseException $e )
+ {
+ $this->assertEquals( "The property 'broken' is write-only.", $e->getMessage() );
+ }
+ }
+
+ public function testBaseValue1()
+ {
+ try
+ {
+ throw new ezcBaseValueException( 'broken', array( 42 ) );
+ }
+ catch ( ezcBaseValueException $e )
+ {
+ $this->assertEquals( "The value 'a:1:{i:0;i:42;}' that you were trying to assign to setting 'broken' is invalid.", $e->getMessage() );
+ }
+ }
+
+ public function testBaseValue2()
+ {
+ try
+ {
+ throw new ezcBaseValueException( 'broken', "string", "strings" );
+ }
+ catch ( ezcBaseValueException $e )
+ {
+ $this->assertEquals( "The value 'string' that you were trying to assign to setting 'broken' is invalid. Allowed values are: strings.", $e->getMessage() );
+ $this->assertEquals( "The value 'string' that you were trying to assign to setting 'broken' is invalid. Allowed values are: strings.", $e->originalMessage );
+ }
+ }
+
+ public function testExtraDirNotFoundException()
+ {
+ try
+ {
+ ezcBase::addClassRepository( 'wrongDir' );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ $this->assertEquals( "The base directory file 'wrongDir' could not be found.", $e->getMessage() );
+ }
+ }
+
+ public function testExtraDirBaseNotFoundException()
+ {
+ try
+ {
+ ezcBase::addClassRepository( '.', './wrongAutoloadDir' );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ $this->assertEquals( "The autoload directory file './wrongAutoloadDir' could not be found.", $e->getMessage() );
+ }
+ }
+
+ public function testBaseAddAndGetAutoloadDirs1()
+ {
+ ezcBase::addClassRepository( '.' );
+ $resultArray = ezcBase::getRepositoryDirectories();
+
+ if ( count( $resultArray ) != 2 )
+ {
+ $this->fail( "Duplicating or missing extra autoload dirs while adding." );
+ }
+
+ if ( !isset( $resultArray['ezc'] ) )
+ {
+ $this->fail( "No packageDir found in result of getRepositoryDirectories()" );
+ }
+
+ if ( !isset( $resultArray[0] ) || $resultArray[0]->basePath != getcwd() )
+ {
+ $this->fail( "Extra base dir '{$resultArray[0]->basePath}' is added incorrectly" );
+ }
+
+ if ( !isset( $resultArray[0] ) || $resultArray[0]->autoloadPath != getcwd() . '/autoload' )
+ {
+ $this->fail( "Extra autoload dir '{$resultArray[0]->autoloadPath}' is added incorrectly" );
+ }
+ }
+
+ // this test is sorta obsolete, but we keep it around for good measure
+ public function testBaseAddAndGetAutoloadDirs2()
+ {
+ ezcBase::addClassRepository( '.', './autoload' );
+ ezcBase::addClassRepository( './Base/tests/test_repository', './Base/tests/test_repository/autoload_files' );
+ ezcBase::addClassRepository( './Base/tests/test_repository', './Base/tests/test_repository/autoload_files' );
+ $resultArray = ezcBase::getRepositoryDirectories();
+
+ if ( count( $resultArray ) != 5 )
+ {
+ $this->fail( "Duplicating or missing extra autoload dirs while adding." );
+ }
+
+ if ( !isset( $resultArray['ezc'] ) )
+ {
+ $this->fail( "No packageDir found in result of getRepositoryDirectories()" );
+ }
+
+ if ( !isset( $resultArray[2] ) || $resultArray[2]->autoloadPath != getcwd() . '/Base/tests/test_repository/autoload_files' )
+ {
+ $this->fail( "Extra autoload dir '{$resultArray[2]->autoloadPath}' is added incorrectly" );
+ }
+
+ self::assertEquals( true, class_exists( 'trBasetestClass', true ) );
+ self::assertEquals( true, class_exists( 'trBasetestClass2', true ) );
+
+ try
+ {
+ self::assertEquals( false, class_exists( 'trBasetestClass3', true ) );
+ self::fail( 'The expected exception was not thrown.' );
+ }
+ catch ( ezcBaseAutoloadException $e )
+ {
+ $cwd = getcwd();
+ self::assertEquals( "Could not find a class to file mapping for 'trBasetestClass3'. Searched for basetest_class3_autoload.php, basetest_autoload.php, autoload.php in: $cwd/autoload, $cwd/autoload, $cwd/autoload, $cwd/Base/tests/test_repository/autoload_files, $cwd/Base/tests/test_repository/autoload_files", $e->getMessage() );
+ }
+
+ self::assertEquals( true, class_exists( 'trBasetestLongClass', true ) );
+
+ try
+ {
+ class_exists( 'trBasetestClass4', true );
+ self::fail( 'The expected exception was not thrown.' );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ self::assertEquals( "The file './Base/tests/test_repository/TestClasses/base_test_class_number_four.php' could not be found.", $e->getMessage() );
+ }
+ }
+
+ public function testBaseAddAndGetAutoloadDirs3()
+ {
+ ezcBase::addClassRepository( './Base/tests/extra_repository', null, 'ext' );
+
+ $resultArray = ezcBase::getRepositoryDirectories();
+ self::assertEquals( true, array_key_exists( 'ezc', $resultArray ) );
+ self::assertEquals( true, array_key_exists( 'ext', $resultArray ) );
+
+ self::assertEquals( true, class_exists( 'extTranslationTest', true ) );
+ self::assertEquals( true, class_exists( 'ezcTranslationTsBackend', true ) );
+ }
+
+ public function testBaseAddAndGetAutoloadDirs4()
+ {
+ ezcBase::addClassRepository( './Base/tests/test_repository', './Base/tests/test_repository/autoload_files', 'tr' );
+
+ try
+ {
+ ezcBase::addClassRepository( './Base/tests/test_repository', './Base/tests/test_repository/autoload_files', 'tr' );
+ }
+ catch ( ezcBaseDoubleClassRepositoryPrefixException $e )
+ {
+ self::assertEquals( "The class repository in './Base/tests/test_repository' (with autoload dir './Base/tests/test_repository/autoload_files') can not be added because another class repository already uses the prefix 'tr'.", $e->getMessage() );
+ }
+
+ $resultArray = ezcBase::getRepositoryDirectories();
+ self::assertEquals( 7, count( $resultArray ) );
+
+ self::assertEquals( true, array_key_exists( 'ezc', $resultArray ) );
+ self::assertEquals( true, array_key_exists( 'tr', $resultArray ) );
+
+ self::assertEquals( getcwd() . '/Base/tests/test_repository', $resultArray['tr']->basePath );
+ self::assertEquals( getcwd() . '/Base/tests/test_repository/autoload_files', $resultArray['tr']->autoloadPath );
+ }
+
+ public function testNoPrefixAutoload()
+ {
+ ezcBase::addClassRepository( './Base/tests/test_repository', './Base/tests/test_repository/autoload_files' );
+ __autoload( 'Object' );
+ if ( !class_exists( 'Object' ) )
+ {
+ $this->fail( "Autoload does not handle classes with no prefix" );
+ }
+ }
+
+ public function testCheckDependencyExtension()
+ {
+ ezcBase::checkDependency( 'Tester', ezcBase::DEP_PHP_EXTENSION, 'standard' );
+ }
+
+ public function testCheckDependencyVersion()
+ {
+ ezcBase::checkDependency( 'Tester', ezcBase::DEP_PHP_VERSION, '5.1.1' );
+ }
+
+ public function testInvalidClass()
+ {
+ try
+ {
+ self::assertEquals( false, class_exists( 'ezcNoSuchClass', true ) );
+ self::fail( 'The expected exception was not thrown.' );
+ }
+ catch ( ezcBaseAutoloadException $e )
+ {
+ $cwd = getcwd();
+ self::assertEquals( "Could not find a class to file mapping for 'ezcNoSuchClass'. Searched for no_such_autoload.php, no_autoload.php, autoload.php in: $cwd/autoload, $cwd/autoload, $cwd/autoload, $cwd/Base/tests/test_repository/autoload_files, $cwd/Base/tests/test_repository/autoload_files, $cwd/Base/tests/extra_repository/autoload, $cwd/Base/tests/test_repository/autoload_files, $cwd/Base/tests/test_repository/autoload_files", $e->getMessage() );
+ }
+ }
+
+ public function testDebug()
+ {
+ try
+ {
+ class_exists( 'ezcTestingOne' );
+ self::fail( "There should have been an exception" );
+ }
+ catch ( ezcBaseAutoloadException $e )
+ {
+ }
+ }
+
+ public function testNoDebug()
+ {
+ try
+ {
+ $options = new ezcBaseAutoloadOptions;
+ $options->debug = false;
+ ezcBase::setOptions( $options );
+
+ class_exists( 'ezcTestingOne' );
+ }
+ catch ( Exception $e )
+ {
+ self::fail( "There should not have been an exception" );
+ }
+ }
+
+ public function testGetInstallationPath()
+ {
+ $path = ezcBase::getInstallationPath();
+ $pathParts = explode( DIRECTORY_SEPARATOR, $path );
+ self::assertEquals( array( 'trunk', '' ), array_splice( $pathParts, -2 ) );
+ self::assertEquals( DIRECTORY_SEPARATOR, substr( $path, -1 ) );
+ }
+
+ public function setup()
+ {
+ $options = new ezcBaseAutoloadOptions;
+ $options->debug = true;
+ ezcBase::setOptions( $options );
+ }
+
+ public function teardown()
+ {
+ $options = new ezcBaseAutoloadOptions;
+ $options->debug = true;
+ ezcBase::setOptions( $options );
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite("ezcBaseTest");
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/extra_repository/Translation/test.php b/include/ezcomponents/Base/tests/extra_repository/Translation/test.php
new file mode 100644
index 000000000..a953fc188
--- /dev/null
+++ b/include/ezcomponents/Base/tests/extra_repository/Translation/test.php
@@ -0,0 +1,5 @@
+
diff --git a/include/ezcomponents/Base/tests/extra_repository/autoload/translation_autoload.php b/include/ezcomponents/Base/tests/extra_repository/autoload/translation_autoload.php
new file mode 100644
index 000000000..8ad317c98
--- /dev/null
+++ b/include/ezcomponents/Base/tests/extra_repository/autoload/translation_autoload.php
@@ -0,0 +1,5 @@
+ 'Translation/test.php',
+);
+?>
diff --git a/include/ezcomponents/Base/tests/features_test.php b/include/ezcomponents/Base/tests/features_test.php
new file mode 100644
index 000000000..8d4e51a9d
--- /dev/null
+++ b/include/ezcomponents/Base/tests/features_test.php
@@ -0,0 +1,145 @@
+markTestSkipped( 'Unix tests' );
+ }
+ }
+
+ public function testSupportsLink()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::supportsLink() );
+ }
+
+ public function testSupportsSymLink()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::supportsSymLink() );
+ }
+
+ public function testSupportsUserId()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::supportsUserId() );
+ }
+
+/* // Need to find a way to make this test work, as setting global enviroment variables
+ // is not working (putenv( "PATH=" ) doesn't unset $_ENV["PATH"])
+ // One solution would be to use in the ezcBaseFeatures::getPath():
+ // getenv( 'PATH' ) instead of $_ENV['PATH'] (but that won't work under IIS).
+ public function testHasImageIdentifyNoPath()
+ {
+ $envPath = getenv( 'PATH' );
+ putenv( "PATH=" );
+ $this->assertEquals( false, ezcBaseFeatures::hasImageIdentify() );
+ putenv( "PATH={$envPath}" );
+ }
+*/
+
+ public function testHasImageConvert()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::hasImageConvert() );
+ }
+
+ public function testGetImageConvertExecutable()
+ {
+ $this->assertEquals( '/usr/bin/convert', ezcBaseFeatures::getImageConvertExecutable() );
+ }
+
+ public function testGetImageIdentifyExecutable()
+ {
+ $this->assertEquals( '/usr/bin/identify', ezcBaseFeatures::getImageIdentifyExecutable() );
+ }
+
+ public function testHasImageIdentify()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::hasImageIdentify() );
+ }
+
+ public function testHasExtensionSupport1()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::hasExtensionSupport( 'standard' ) );
+ }
+
+ public function testHasExtensionSupportNotFound1()
+ {
+ $this->assertEquals( false, ezcBaseFeatures::hasExtensionSupport( 'non_existent_extension' ) );
+ try
+ {
+ throw new ezcBaseExtensionNotFoundException( 'non_existent_extension', null, 'This is just a test.' );
+ }
+ catch ( ezcBaseExtensionNotFoundException $e )
+ {
+ $this->assertEquals( "The extension 'non_existent_extension' could not be found. This is just a test.",
+ $e->getMessage() );
+ }
+ }
+
+ public function testHasExtensionSupportNotFound2()
+ {
+ $this->assertEquals( false, ezcBaseFeatures::hasExtensionSupport( 'non_existent_extension' ) );
+ try
+ {
+ throw new ezcBaseExtensionNotFoundException( 'non_existent_extension', '1.2', 'This is just a test.' );
+ }
+ catch ( ezcBaseExtensionNotFoundException $e )
+ {
+ $this->assertEquals( "The extension 'non_existent_extension' with version '1.2' could not be found. This is just a test.",
+ $e->getMessage() );
+ }
+ }
+
+ public function testHasFunction1()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::hasFunction( 'function_exists' ) );
+ }
+
+ public function testHasFunction2()
+ {
+ $this->assertEquals( false, ezcBaseFeatures::hasFunction( 'non_existent_function_in_php' ) );
+ }
+
+ public function testHasExtensionSupport2()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::hasExtensionSupport( 'date', '5.1.0' ) );
+ }
+
+ public function testClassExists()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::classExists( 'Exception', false ) );
+ }
+
+ public function testClassExistsAutoload()
+ {
+ $this->assertEquals( true, ezcBaseFeatures::classExists( 'ezcBaseFeatures' ) );
+ }
+
+ public function testClassExistsNotFound()
+ {
+ $this->assertEquals( false, ezcBaseFeatures::classExists( 'ezcBaseNonExistingClass', false ) );
+ }
+
+ public function testClassExistsNotFoundAutoload()
+ {
+ $this->assertEquals( false, ezcBaseFeatures::classExists( 'ezcBaseNonExistingClass' ) );
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite("ezcBaseFeaturesTest");
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/file_calculate_relative_path_test.php b/include/ezcomponents/Base/tests/file_calculate_relative_path_test.php
new file mode 100644
index 000000000..dff495fd4
--- /dev/null
+++ b/include/ezcomponents/Base/tests/file_calculate_relative_path_test.php
@@ -0,0 +1,67 @@
+
diff --git a/include/ezcomponents/Base/tests/file_copy_recursive_test.php b/include/ezcomponents/Base/tests/file_copy_recursive_test.php
new file mode 100644
index 000000000..095cc8c01
--- /dev/null
+++ b/include/ezcomponents/Base/tests/file_copy_recursive_test.php
@@ -0,0 +1,234 @@
+tempDir = $this->createTempDir( __CLASS__ );
+ mkdir( $this->tempDir . '/dir1' );
+ mkdir( $this->tempDir . '/dir2' );
+ mkdir( $this->tempDir . '/dir2/dir1' );
+ mkdir( $this->tempDir . '/dir2/dir1/dir1' );
+ mkdir( $this->tempDir . '/dir2/dir2' );
+ mkdir( $this->tempDir . '/dir4' );
+ mkdir( $this->tempDir . '/dir5' );
+ mkdir( $this->tempDir . '/dir6' );
+ file_put_contents( $this->tempDir . '/dir1/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir1/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir1/.file3.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir1/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir1/dir1/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir1/dir1/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir2/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir4/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir4/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir5/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir5/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir6/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir6/file2.txt', 'test' );
+ chmod( $this->tempDir . '/dir4/file1.txt', 0 );
+ chmod( $this->tempDir . '/dir5', 0 );
+ chmod( $this->tempDir . '/dir6', 0400 );
+ }
+
+ protected function tearDown()
+ {
+ chmod( $this->tempDir . '/dir5', 0700 );
+ chmod( $this->tempDir . '/dir6', 0700 );
+ $this->removeTempDir();
+ }
+
+ public function testRecursiveCopyEmptyDir()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir1',
+ $this->tempDir . '/dest'
+ );
+
+ $this->assertEquals(
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dir1' ) ),
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dest' ) )
+ );
+
+ $this->assertSame(
+ 0775,
+ fileperms( $this->tempDir . '/dest' ) & 0777,
+ 'Directory mode should equal 0775.'
+ );
+ }
+
+ public function testRecursiveCopyFile()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir1/file1.txt',
+ $this->tempDir . '/dest'
+ );
+
+ $this->assertTrue(
+ is_file( $this->tempDir . '/dest' )
+ );
+
+ $this->assertSame(
+ 0664,
+ fileperms( $this->tempDir . '/dest' ) & 0777,
+ 'File mode should equal 0664.'
+ );
+ }
+
+ public function testRecursiveCopyEmptyDirMode()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir1',
+ $this->tempDir . '/dest',
+ -1,
+ 0777,
+ 0777
+ );
+
+ $this->assertEquals(
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dir1' ) ),
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dest' ) )
+ );
+
+ $this->assertSame(
+ 0777,
+ fileperms( $this->tempDir . '/dest' ) & 0777,
+ 'Directory mode should equal 0777.'
+ );
+ }
+
+ public function testRecursiveCopyFileMode()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir1/file1.txt',
+ $this->tempDir . '/dest',
+ -1,
+ 0777,
+ 0777
+ );
+
+ $this->assertTrue(
+ is_file( $this->tempDir . '/dest' )
+ );
+
+ $this->assertSame(
+ 0777,
+ fileperms( $this->tempDir . '/dest' ) & 0777,
+ 'File mode should equal 0777.'
+ );
+ }
+
+ public function testRecursiveCopyFullDir()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir2',
+ $this->tempDir . '/dest'
+ );
+
+ $this->assertEquals(
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dir2' ) ),
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dest' ) )
+ );
+ }
+
+ public function testRecursiveCopyFullDirDepthZero()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir2',
+ $this->tempDir . '/dest',
+ 0
+ );
+
+ $this->assertEquals(
+ 0,
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dest' ) )
+ );
+
+ $this->assertTrue(
+ is_dir( $this->tempDir . '/dest' )
+ );
+ }
+
+ public function testRecursiveCopyFullDirLimitedDepth()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir2',
+ $this->tempDir . '/dest',
+ 2
+ );
+
+ $this->assertEquals(
+ 3,
+ count( ezcBaseFile::findRecursive( $this->tempDir . '/dest' ) )
+ );
+ }
+
+ public function testRecursiveCopyFailureNotExisting()
+ {
+ try
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/not_existing',
+ $this->tempDir . '/dest'
+ );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ return;
+ }
+
+ $this->fail( 'Expected ezcBaseFileNotFoundException.' );
+ }
+
+ public function testRecursiveCopyFailureNotReadable()
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir5',
+ $this->tempDir . '/dest'
+ );
+
+ $this->assertFalse(
+ is_dir( $this->tempDir . '/dest' )
+ );
+
+ $this->assertFalse(
+ is_file( $this->tempDir . '/dest' )
+ );
+ }
+
+ public function testRecursiveCopyFailureNotWriteable()
+ {
+ try
+ {
+ ezcBaseFile::copyRecursive(
+ $this->tempDir . '/dir2',
+ $this->tempDir . '/dir4'
+ );
+ }
+ catch ( ezcBaseFilePermissionException $e )
+ {
+ return;
+ }
+
+ $this->fail( 'Expected ezcBaseFilePermissionException.' );
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite( __CLASS__ );
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/file_find_recursive_test.php b/include/ezcomponents/Base/tests/file_find_recursive_test.php
new file mode 100644
index 000000000..82b2adedd
--- /dev/null
+++ b/include/ezcomponents/Base/tests/file_find_recursive_test.php
@@ -0,0 +1,142 @@
+ 'File/CREDITS',
+ 1 => 'File/ChangeLog',
+ 2 => 'File/DESCRIPTION',
+ 3 => 'File/design/class_diagram.png',
+ 4 => 'File/design/design.txt',
+ 5 => 'File/design/file.xml',
+ 6 => 'File/design/file_operations.png',
+ 7 => 'File/design/md5.png',
+ 8 => 'File/design/requirements.txt',
+ 9 => 'File/src/file.php',
+ 10 => 'File/src/file_autoload.php',
+ 11 => 'File/tests/file_calculate_relative_path_test.php',
+ 12 => 'File/tests/file_find_recursive_test.php',
+ 13 => 'File/tests/file_remove_recursive_test.php',
+ 14 => 'File/tests/suite.php',
+ );
+ self::assertEquals( $expected, ezcBaseFile::findRecursive( "File", array(), array( '@/docs/@', '@svn@', '@\.swp$@' ), $stats ) );
+ self::assertEquals( array( 'size' => 130984, 'count' => 15 ), $stats );
+ }
+
+ public function testRecursive2()
+ {
+ $expected = array (
+ 0 => './File/CREDITS',
+ 1 => './File/ChangeLog',
+ 2 => './File/DESCRIPTION',
+ 3 => './File/design/class_diagram.png',
+ 4 => './File/design/design.txt',
+ 5 => './File/design/file.xml',
+ 6 => './File/design/file_operations.png',
+ 7 => './File/design/md5.png',
+ 8 => './File/design/requirements.txt',
+ 9 => './File/src/file.php',
+ 10 => './File/src/file_autoload.php',
+ 11 => './File/tests/file_calculate_relative_path_test.php',
+ 12 => './File/tests/file_find_recursive_test.php',
+ 13 => './File/tests/file_remove_recursive_test.php',
+ 14 => './File/tests/suite.php',
+ );
+ self::assertEquals( $expected, ezcBaseFile::findRecursive( ".", array( '@^\./File/@' ), array( '@/docs/@', '@\.svn@', '@\.swp$@' ), $stats ) );
+ self::assertEquals( array( 'size' => 130984, 'count' => 15 ), $stats );
+ }
+
+ public function testRecursive3()
+ {
+ $expected = array (
+ 0 => 'File/design/class_diagram.png',
+ 1 => 'File/design/file_operations.png',
+ 2 => 'File/design/md5.png',
+ );
+ self::assertEquals( $expected, ezcBaseFile::findRecursive( "File", array( '@\.png$@' ), array( '@\.svn@' ), $stats ) );
+ self::assertEquals( array( 'size' => 17642, 'count' => 3 ), $stats );
+ }
+
+ public function testRecursive4()
+ {
+ $expected = array (
+ 0 => 'File/design/class_diagram.png',
+ 1 => 'File/design/design.txt',
+ 2 => 'File/design/file.xml',
+ 3 => 'File/design/file_operations.png',
+ 4 => 'File/design/md5.png',
+ 5 => 'File/design/requirements.txt',
+ );
+ self::assertEquals( $expected, ezcBaseFile::findRecursive( "File", array( '@/design/@' ), array( '@\.svn@' ), $stats ) );
+ self::assertEquals( array( 'size' => 114282, 'count' => 6 ), $stats );
+ }
+
+ public function testRecursive5()
+ {
+ $expected = array (
+ 0 => 'File/design/design.txt',
+ 1 => 'File/design/requirements.txt',
+ 2 => 'File/src/file.php',
+ 3 => 'File/src/file_autoload.php',
+ 4 => 'File/tests/file_calculate_relative_path_test.php',
+ 5 => 'File/tests/file_find_recursive_test.php',
+ 6 => 'File/tests/file_remove_recursive_test.php',
+ 7 => 'File/tests/suite.php',
+ );
+ self::assertEquals( $expected, ezcBaseFile::findRecursive( "File", array( '@\.(php|txt)$@' ), array( '@/docs/@', '@\.svn@' ) ) );
+ }
+
+ public function testRecursive6()
+ {
+ $expected = array();
+ self::assertEquals( $expected, ezcBaseFile::findRecursive( "File", array( '@xxx@' ) ) );
+ }
+
+ public function testNonExistingDirectory()
+ {
+ $expected = array();
+ try
+ {
+ ezcBaseFile::findRecursive( "NotHere", array( '@xxx@' ) );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ self::assertEquals( "The directory file 'NotHere' could not be found.", $e->getMessage() );
+ }
+ }
+
+ public function testStatsEmptyArray()
+ {
+ $expected = array (
+ 0 => 'File/design/class_diagram.png',
+ 1 => 'File/design/design.txt',
+ 2 => 'File/design/file.xml',
+ 3 => 'File/design/file_operations.png',
+ 4 => 'File/design/md5.png',
+ 5 => 'File/design/requirements.txt',
+ );
+ $stats = array();
+ self::assertEquals( $expected, ezcBaseFile::findRecursive( "File", array( '@/design/@' ), array( '@\.svn@' ), $stats ) );
+ self::assertEquals( array( 'size' => 114282, 'count' => 6 ), $stats );
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite( "ezcBaseFileFindRecursiveTest" );
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/file_is_absolute_path.php b/include/ezcomponents/Base/tests/file_is_absolute_path.php
new file mode 100644
index 000000000..1e2acd510
--- /dev/null
+++ b/include/ezcomponents/Base/tests/file_is_absolute_path.php
@@ -0,0 +1,224 @@
+
diff --git a/include/ezcomponents/Base/tests/file_remove_recursive_test.php b/include/ezcomponents/Base/tests/file_remove_recursive_test.php
new file mode 100644
index 000000000..55b24643f
--- /dev/null
+++ b/include/ezcomponents/Base/tests/file_remove_recursive_test.php
@@ -0,0 +1,131 @@
+tempDir = $this->createTempDir( 'ezcBaseFileRemoveFileRecursiveTest' );
+ mkdir( $this->tempDir . '/dir1' );
+ mkdir( $this->tempDir . '/dir2' );
+ mkdir( $this->tempDir . '/dir2/dir1' );
+ mkdir( $this->tempDir . '/dir2/dir1/dir1' );
+ mkdir( $this->tempDir . '/dir2/dir2' );
+ mkdir( $this->tempDir . '/dir4' );
+ mkdir( $this->tempDir . '/dir5' );
+ mkdir( $this->tempDir . '/dir6' );
+ file_put_contents( $this->tempDir . '/dir1/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir1/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir1/.file3.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir1/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir1/dir1/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir1/dir1/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir2/dir2/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir4/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir4/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir5/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir5/file2.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir6/file1.txt', 'test' );
+ file_put_contents( $this->tempDir . '/dir6/file2.txt', 'test' );
+ chmod( $this->tempDir . '/dir4/file1.txt', 0 );
+ chmod( $this->tempDir . '/dir5', 0 );
+ chmod( $this->tempDir . '/dir6', 0400 );
+ }
+
+ protected function tearDown()
+ {
+ chmod( $this->tempDir . '/dir5', 0700 );
+ chmod( $this->tempDir . '/dir6', 0700 );
+ $this->removeTempDir();
+ }
+
+ public function testRecursive1()
+ {
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ ezcBaseFile::removeRecursive( $this->tempDir . '/dir1' );
+ self::assertEquals( 9, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ ezcBaseFile::removeRecursive( $this->tempDir . '/dir2' );
+ self::assertEquals( 4, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ }
+
+ public function testRecursive2()
+ {
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ try
+ {
+ ezcBaseFile::removeRecursive( $this->tempDir . '/dir3' );
+ }
+ catch ( ezcBaseFileNotFoundException $e )
+ {
+ self::assertEquals( "The directory file '{$this->tempDir}/dir3' could not be found.", $e->getMessage() );
+ }
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ }
+
+ public function testRecursive3()
+ {
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ try
+ {
+ ezcBaseFile::removeRecursive( $this->tempDir . '/dir4' );
+ }
+ catch ( ezcBaseFilePermissionException $e )
+ {
+ self::assertEquals( "The file '{$this->tempDir}/dir5' can not be opened for reading.", $e->getMessage() );
+ }
+ self::assertEquals( 10, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ }
+
+ public function testRecursive4()
+ {
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ try
+ {
+ ezcBaseFile::removeRecursive( $this->tempDir . '/dir5' );
+ }
+ catch ( ezcBaseFilePermissionException $e )
+ {
+ self::assertEquals( "The file '{$this->tempDir}/dir5' can not be opened for reading.", $e->getMessage() );
+ }
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ }
+
+ public function testRecursive5()
+ {
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ try
+ {
+ ezcBaseFile::removeRecursive( $this->tempDir . '/dir6' );
+ }
+ catch ( ezcBaseFilePermissionException $e )
+ {
+ // Make no asumption on which file is tryed to be removed first
+ self::assertEquals(
+ 1,
+ preg_match(
+ "(The file '{$this->tempDir}/dir6/file[12].txt' can not be removed.)",
+ $e->getMessage()
+ )
+ );
+ }
+ self::assertEquals( 12, count( ezcBaseFile::findRecursive( $this->tempDir ) ) );
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite( "ezcBaseFileRemoveRecursiveTest" );
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/init/base_init_callback.php b/include/ezcomponents/Base/tests/init/base_init_callback.php
new file mode 100644
index 000000000..3724adf16
--- /dev/null
+++ b/include/ezcomponents/Base/tests/init/base_init_callback.php
@@ -0,0 +1,23 @@
+configured = true;
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/init/base_init_class.php b/include/ezcomponents/Base/tests/init/base_init_class.php
new file mode 100644
index 000000000..6567d4fba
--- /dev/null
+++ b/include/ezcomponents/Base/tests/init/base_init_class.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/Base/tests/struct_test.php b/include/ezcomponents/Base/tests/struct_test.php
new file mode 100644
index 000000000..b14552cff
--- /dev/null
+++ b/include/ezcomponents/Base/tests/struct_test.php
@@ -0,0 +1,53 @@
+no_such_property = 'value';
+ $this->fail( 'Expected exception was not thrown.' );
+ }
+ catch ( ezcBasePropertyNotFoundException $e )
+ {
+ $this->assertEquals( "No such property name 'no_such_property'.", $e->getMessage() );
+ }
+
+ try
+ {
+ $value = $struct->no_such_property;
+ $this->fail( 'Expected exception was not thrown.' );
+ }
+ catch ( ezcBasePropertyNotFoundException $e )
+ {
+ $this->assertEquals( "No such property name 'no_such_property'.", $e->getMessage() );
+ }
+ }
+
+ public function testBaseRepositoryDirectorySetState()
+ {
+ $dir = ezcBaseRepositoryDirectory::__set_state( array( 'type' => ezcBaseRepositoryDirectory::TYPE_EXTERNAL, 'basePath' => '/tmp', 'autoloadPath' => '/tmp/autoload' ) );
+ $this->assertEquals( ezcBaseRepositoryDirectory::TYPE_EXTERNAL, $dir->type );
+ $this->assertEquals( '/tmp', $dir->basePath );
+ $this->assertEquals( '/tmp/autoload', $dir->autoloadPath );
+ }
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite( "ezcBaseStructTest" );
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/suite.php b/include/ezcomponents/Base/tests/suite.php
new file mode 100644
index 000000000..623914dc4
--- /dev/null
+++ b/include/ezcomponents/Base/tests/suite.php
@@ -0,0 +1,49 @@
+setName("Base");
+
+ $this->addTest( ezcBaseTest::suite() );
+ $this->addTest( ezcBaseInitTest::suite() );
+ $this->addTest( ezcBaseFeaturesTest::suite() );
+ $this->addTest( ezcBaseOptionsTest::suite() );
+ $this->addTest( ezcBaseStructTest::suite() );
+ $this->addTest( ezcBaseFileCalculateRelativePathTest::suite() );
+ $this->addTest( ezcBaseFileFindRecursiveTest::suite() );
+ $this->addTest( ezcBaseFileIsAbsoluteTest::suite() );
+ $this->addTest( ezcBaseFileCopyRecursiveTest::suite() );
+ $this->addTest( ezcBaseFileRemoveRecursiveTest::suite() );
+ }
+
+ public static function suite()
+ {
+ return new ezcBaseSuite();
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/test_options.php b/include/ezcomponents/Base/tests/test_options.php
new file mode 100644
index 000000000..c86a01789
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_options.php
@@ -0,0 +1,18 @@
+ "bar" );
+
+ public function __set( $propertyName, $propertyValue )
+ {
+ if ( $this->__isset( $propertyName ) )
+ {
+ $this->properties[$propertyName] = $propertyValue;
+ }
+ else
+ {
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ }
+ }
+}
+?>
diff --git a/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_class.php b/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_class.php
new file mode 100644
index 000000000..cd91ad5a0
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_class.php
@@ -0,0 +1,4 @@
+
diff --git a/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_class_number_two.php b/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_class_number_two.php
new file mode 100644
index 000000000..0e86a858b
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_class_number_two.php
@@ -0,0 +1,4 @@
+
diff --git a/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_long_class.php b/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_long_class.php
new file mode 100644
index 000000000..a5712a304
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_repository/TestClasses/base_test_long_class.php
@@ -0,0 +1,4 @@
+
diff --git a/include/ezcomponents/Base/tests/test_repository/autoload_files/basetest_autoload.php b/include/ezcomponents/Base/tests/test_repository/autoload_files/basetest_autoload.php
new file mode 100644
index 000000000..93279df78
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_repository/autoload_files/basetest_autoload.php
@@ -0,0 +1,7 @@
+ 'TestClasses/base_test_class.php',
+ 'trBasetestClass2' => 'TestClasses/base_test_class_number_two.php',
+ 'trBasetestClass4' => 'TestClasses/base_test_class_number_four.php',
+);
+?>
diff --git a/include/ezcomponents/Base/tests/test_repository/autoload_files/basetest_long_autoload.php b/include/ezcomponents/Base/tests/test_repository/autoload_files/basetest_long_autoload.php
new file mode 100644
index 000000000..52b73ac9b
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_repository/autoload_files/basetest_long_autoload.php
@@ -0,0 +1,5 @@
+ 'TestClasses/base_test_long_class.php',
+);
+?>
diff --git a/include/ezcomponents/Base/tests/test_repository/autoload_files/object_autoload.php b/include/ezcomponents/Base/tests/test_repository/autoload_files/object_autoload.php
new file mode 100644
index 000000000..5e3dc908e
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_repository/autoload_files/object_autoload.php
@@ -0,0 +1,5 @@
+ 'object/object.php',
+);
+?>
diff --git a/include/ezcomponents/Base/tests/test_repository/object/object.php b/include/ezcomponents/Base/tests/test_repository/object/object.php
new file mode 100644
index 000000000..3b46ba6e4
--- /dev/null
+++ b/include/ezcomponents/Base/tests/test_repository/object/object.php
@@ -0,0 +1,4 @@
+
diff --git a/include/ezcomponents/ChangeLog b/include/ezcomponents/ChangeLog
new file mode 100644
index 000000000..b2ed56560
--- /dev/null
+++ b/include/ezcomponents/ChangeLog
@@ -0,0 +1,1055 @@
+SVN Revision: 8406
+
+
+Archive
+=======
+
+1.3 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13137: Subsequent files where seen as hardlinks on Windows
+ because inodes do not exist.
+
+
+1.3beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12818: Add the possibility to open archives in read-only
+ mode.
+
+
+
+Authentication
+==============
+
+1.2 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.2rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.2beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12992: LDAP registerFetchData() now correctly uses normal case
+ attributes (eg. 'displayName').
+
+
+1.2alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature request #12935: Added the returnUrl OpenID option to
+ customize the return URL on OpenID authentification.
+- Fixed the issue where a failed connection to an LDAP server would not be
+ detected.
+
+
+
+Base
+====
+
+1.5 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.5rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.5beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Added the ezcBasePersistable interface that can be used to ensure that the
+ object implementing this interface can be used with PersistentObject and
+ Search.
+
+
+1.5alpha2 - Tuesday 13 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed a bug in ezcBaseFile::findRecursive that prevented you from passing an
+ empty array to collect statistics.
+- Changed ezcBase::getInstallationPath() so that it always returns a trailing
+ directory separator.
+
+
+1.5alpha1 - Monday 07 April 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #8529: Added a by-reference argument to
+ ezcBaseFile::findRecursive that returns statistsics (count and total size)
+ of all files that are returned by this function.
+- Implemented issue #11506: Added the static method
+ ezcBase::getInstallationPath().
+- Implemented issue #12694: replace reflection test for class type with spl
+ function.
+
+
+
+Cache
+=====
+
+1.4 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.4rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13053: Fixed calculation of remaining life time in all storage
+ classes and with that resolved the strange behavior in the Memcach storage.
+- Fixed issue #13112: Corrected the tutorial text.
+- Fixed issue #13119: Replaced references to /tmp with createTempDir() calls.
+
+
+1.4beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12993: Hierarchical caching does not prune items in the correct
+ situation. Note that the internal structure of the stack meta data changed
+ drastically with this fix. You need to reset() the whole storage after this
+ update.
+
+
+1.4alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature #12587: Hierachic caching for the Cache component. The
+ class ezcCacheStack and related classes provide that feature.
+- Fixed issue #12666: ezcCacheStorageMemcache potentially creates multiple
+ connections to the same memcached server.
+
+
+
+Configuration
+=============
+
+1.3 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.3beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12694: replace reflection test for class type with spl
+ function.
+- Implemented issue #12911: Added the hasGroup() and getSettingsInGroup()
+ methods to ezcConfigurationManager to allow for fetching of all settings
+ in a group. (Patch by James Pic)
+
+
+1.2.1 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12477: missing backslash \ in file format part of configuration
+ component tutorial.
+
+
+
+ConsoleTools
+============
+
+1.4 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.4rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13118: Class::method() replaced by Class->method() for object
+ method calls and attribute accesses where appropriate.
+
+
+1.4beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.4alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #10765: ezcConsoleQuestionDialog::YesNoQuestion does not
+ accept "Yes" or "yes" / "no", "No" as valid answer. A new validator
+ (ezcConsoleQuestionMappingValidator) was introduced for this, which extends
+ ezcConsoleQuestionCollectionValidator and is now used for the yes-no-question.
+- Implemented issue #10791: ezcConsoleTable should implement __toString.
+- Implemented issue #10838: ezcConsoleOutputFormats should implement Iterator
+ and Countable.
+- Fixed issue #12561: getSynopsis() bugs when at least 2 options don't have
+ short-names.
+- Fixed issue #12623: console menu dialog handles unexisting options wrong.
+- Fixed issue #12624: Wrong text in documentation of eZConsoleDialogViewer.
+- Fixed issue #12625: console tools lacks documentation on targets.
+- Fixed issue #12626: Type "ta" in ezconsoleoutput documentation.
+- Fixed issue #12628: Missing space in ezcConsoleInput::getHelp API doc.
+- Fixed issue #12629: Method name not replaced with link in
+ ezcConsoleMenuDialog::display() API doc.
+- Fixed issue #12630: Add link to ezcDialogNoValidResultException in
+ ezcConsoleDialog::getResult() API doc.
+- Fxied issue #12636: ezcConsoleOutput->outputLine() prints EOL always to
+ STDOUT instead of the defined target.
+
+
+
+Database
+========
+
+1.4 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13188: Sub select queries are no more quoted in SQLite IN
+ statements.
+
+
+1.4rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.4beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12150: Connect to MSSQL database fails due to wrong driver
+ specification.
+
+
+1.4alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #10753: ezcDbQuery should implement __toString().
+- Implemented issue #12540: subselect support for other query types besides
+ SELECT.
+
+
+
+DatabaseSchema
+==============
+
+1.4 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.4rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12937: PersistentObject definition writer now creates correct
+ PHP type definition for non-number primary keys.
+- Fixed issue #13072: SQLite schema reader does not work with uppercase type
+ names.
+
+
+1.4beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Changed the class name ezcDbSchemaNonUniqueIndexNameValidator to
+ ezcDbSchemaUniqueIndexNameValidator.
+- Changed ezcDbSchemaDbReader back to an interface, and created a common SQL
+ reader class that implements the common methods.
+- Moved ezcDbSchema*Reader/getReaderType() to the new common SQL reader class
+ ezcDbSchemaCommonSqlReader.
+- Created a common method ezcDbSchemaCommonSqlReader::processSchema() that
+ iterates over all the tables and gathers information about them and their
+ indexes.
+
+
+1.4alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #10069: DatabaseSchema only for particular tables.
+- Implemented issue #10364: Added support for table name prefixes.
+- Implemented issue #11562: Schema validator for duplicate index names.
+- Implemented issue #12694: Replace reflection test for class type with SPL
+ function.
+- Fixed bug #12538: No warning is thrown when an unsupported type is found
+ while reading from a database, or writing to a database.
+
+
+
+Debug
+=====
+
+1.2 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.2rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.2beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.2alpha1 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented enhancement #10701: Getting a readable backtrace. The
+ ezcDebug->log() method now allows you to selectively add a stack trace to
+ debug log messages. The new ezcDebugOptions class allows to globally switch
+ on stack traces for all log messages.
+- Fixed issue #12427: Changed output rendering of the debug formatter to not
+ include a stylesheet by default, but instead the formatter now includes easy
+ overridable CSS classes.
+
+
+Document
+========
+
+1.0 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13164: Handle dense enumerated lists.
+
+
+1.0rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13167: Correctly handle indetation change after definition
+ lists.
+- Fixed issue #13166: Handle embeddded URLs in inline references.
+
+
+1.0alpha1 - Tuesday 13 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Initial release of this package.
+
+EventLog
+========
+
+1.3 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.3beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3alpha3 - Tuesday 13 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13008: ezcLogFileWriter::openFile() throws wrong Exception.
+
+
+1.3alpha2 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature #12907: Using both trigger_error() and all available
+ error types. (Patch by James Pic)
+- Implemented feature: Trim spaces around source.
+
+
+1.3alpha1 - Monday 07 April 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature #12503: Ability to disable log rotation by setting the
+ max log file option to the constructor to false.
+
+
+
+Execution
+=========
+
+1.1.1 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12694: Replace reflection test for class type with SPL
+ function.
+
+
+Feed
+====
+
+1.1 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.1rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- ATOM generator considers feed entry links without the rel attribute set as
+ rel="alternate" by default.
+- Fixed issue #13109: Fixed RSS1 (RDF) parsing when the "resource" attribute
+ is specified as "rdf:resource".
+- Fixed iTunes module elements handling if using a different prefix than
+ 'itunes'.
+- Fixed issue #13110: Added redirection support for feed URLs returning the
+ 301, 302 and 307 HTTP headers.
+
+
+1.1beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Removed ezcFeedCanNotParseException class, using ezcFeedParseErrorException
+ instead.
+- Changed protected methods and variables from processor classes to private.
+- Changed get() and set() functions from ezcFeedProcessor into __get() and
+ __set(), and added __isset().
+- Added the registerFeed() and registerModule() functions to ezcFeed.
+- Removed plural properties from ezcFeed (authors, categories, contributors,
+ items, links).
+- Added feed element types (person, category, link, image, etc) to specify
+ different feed and module elements.
+- Removed the method set() from ezcFeed and ezcFeedItem.
+- Removed the class ezcFeedTools.
+- Renamed ezcFeedItem to ezcFeedEntryElement.
+- Added a feed type to the ezcFeed method generate() to specify the output
+ feed type, and made the ezcFeed constructor feed type argument optional.
+- The properties are stored in ezcFeed and are assigned in the same way
+ without being dependent on the feed type.
+
+
+1.1alpha1 - Monday 07 April 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Reorganized internal structure of classes.
+- Added ezcFeedSchema to define different feed types.
+- Added ezcFeedElement instead of ezcFeedItem.
+- Added support for XML attributes for feeds.
+- Added regression tests based on the ones from Template.
+- In RSS2 the title, link and description nodes of item nodes are not all
+ required, but at least one of title or description nodes is required.
+- Added support for RSS2 enclosure and tutorial on how to use it for creating
+ and parsing podcasts.
+- Completed support for creating and parsing RSS2 feeds.
+- Completed support for creating and parsing RSS1 feeds.
+- Completed support for creating and parsing ATOM feeds.
+- Added the getContentType() method in ezcFeed to return its Content-Type.
+- Added support for ATOM xml:lang attribute.
+- The RSS1 about attribute is accessed as id through ezcFeed.
+- Added support for the Content module.
+- Added support for the DublinCore module.
+- Fixed issue #12557: added parse support for version 0.91 and 0.92 RSS feeds,
+ which are considered as RSS2 feeds.
+- Added support for the iTunes module.
+- Added support for the CreativeCommons module.
+- Added the required xmlns attribute when creating RSS1 feeds.
+- Added parse support for version 0.93 and 0.94 RSS feeds.
+- The RSS2 guid item attribute is accessed as id through ezcFeed.
+- Added a feed creator example application.
+- Changed the method ezcFeedTools::prepareDate() to return a DateTime object
+ - PHP 5.2 is required now.
+- Added generator information for ATOM when generating the feed.
+
+
+1.0beta1 - Monday 18 December 2006
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Added the ezcFeed::parseContent() method that parses a feed contained in a
+ variable.
+
+
+1.0alpha1 - Tuesday 24 October 2006
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Initial release of this package.
+
+Graph
+=====
+
+1.3 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12581: Wrong axis captions in bar charts with manual min/max
+ values
+
+
+1.3beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature #10829: Get resource from driver.
+- Implemented feature #10957: Embed glyphs for exact SVG font width
+ estimation.
+ (Patch by Freddie Witherden)
+- Implemented feature #11979: Line width configurable per data set.
+- Implemented feature #12382: Enhance line chart to allow invisible lines.
+- Fixed issue #12483: Average dataset does not work with a single value.
+
+
+1.2.1 - Monday 21 January 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature #9407: Cairo driver.
+- Fixed issue #11777: Optionally independent axis font configuration.
+- Fixed issue #12254: Bad property-check for strokeLineJoin in SVG driver.
+- Fixed issue #12295: Broken automatic scaling with manually set min value,
+ not divisible by major step.
+- Fixed issue #12326: Per datapoint colors not used with bar charts.
+- Fixed issue #12405: Highlightfontsize reduced when padding > 0 in highlight
+ font options.
+
+
+
+ImageAnalysis
+=============
+
+1.1.3 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed bug #12638: ezcImageAnalyzerImagemagickHandler::checkImagemagick
+ method missing SunOS in switch.
+
+
+
+ImageConversion
+===============
+
+1.3.5 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13134: Fixed array_splice() call in
+ ezcImageTransformation->addFilter().
+
+
+1.3.4 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12592: New thumbnail filter. Documentation about the scale()
+ filter has been added to the thumbnail filters.
+- Fixed issue #12667: ezcImageConverter doesn't pass saveOptions to
+ ezcImageTransformation.
+- Fixed issue #12671: Unhandled exception in ezcImageTransformation. Checks to
+ avoid double throwing of exceptions have been introduced. Additional
+ parameter checks are performed.
+
+
+
+Mail
+====
+
+1.5 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.5rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13038: Added support for non-ascii and mime-emcoded (non-RFC)
+ filenames for mail attachments.
+
+
+1.5beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13010: The transport connection handles correcly cases where
+ CRLF is split in 2 different blocks read from server.
+
+
+1.5alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12844: getTmpDir() not properly set in Windows.
+- Fixed issue #12903: The mail digest size is not calculated twice anymore.
+- Fixed issue #12930: The SMTP authentication methods are used in correct
+ strength order now.
+- Implemented feature request #11937: Switch to turn off automatic inclusion
+ of files with the Mail Composer.
+- Implemented feature request #12203: Replaced hard-coded paths for temporary
+ directory with the PHP 5.2.1 function sys_get_temp_dir().
+- Implemented feature request #12694: Replace reflection test for class type
+ with SPL function.
+
+
+1.4.3 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12595: Folding is no longer applied twice for To, Cc and Bcc
+ headers.
+
+
+1.4.2 - Thursday 17 January 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12372: MTA transport does not encode subject.
+
+
+
+PersistentObject
+================
+
+1.4 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.4rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.4beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.4alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Refactored ezcPersistentSession.
+- Implemented enhancement #10151: Improved Database and PersistentObject
+ datatype support (especially binary data). The object definitions allow to
+ specify the datatype of the database column assigned to a property.
+- Implemented enhancement #10373: Several relations to the same table for
+ PersistentObject.
+- Implemented enhancement #10727: Improved error messages for PersistentObject
+ with ManualGenerator.
+- Fixed issue #10205: Binding variables with an undeclared var doesn't throw
+ an exception. If getState() on a persistent object does not return an array
+ an exception is thrown now.
+- Fixed issue #12471: PersistentObject misses support for boolean datatype.
+
+
+Search
+======
+
+1.0 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.0rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Changed the way how ezcSearchQueryBuilder works. You will now have to
+ run parseSearchQuery() instead of just passing the query object, query
+ string and fields to the constructor.
+- Changed the nested array result for documents into a structure where each
+ ezcSearchResult contains an array of ezcSearchResultDocument objects, which
+ on its turn contain the score, highlighting and document itself.
+- Changed the default limit fo returned search results for the Solr handler
+ from 10 to unlimited (well, 999999).
+
+
+1.0alpha2 - Tuesday 13 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixes to the Solr handler:
+
+ - Implemented missing "order by" support.
+ - Fixed between support.
+ - Fixed boost support in case a field already had a boost value assigned by
+ default.
+
+
+1.0alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Initial release of this package.
+
+
+SignalSlot
+==========
+
+1.1.1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed bug #12676: Wrong link under "callback" in SignalSlot tutorial.
+
+
+
+SystemInformation
+=================
+
+1.0.6 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed retrieving CPU count, type and speed on Windows Vista.
+
+
+
+Template
+========
+
+1.3 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.3beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Changed the date_format() template function to only accept DateTime objects.
+
+
+1.3alpha2 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12723: Make Template date functions support the DateTime
+ object.
+- Fixed issue #11152: ezcTemplateLocationInterface should not have "Interface"
+ in the classname.
+- Fixed issue #12322: Template regression between 1.1 and 1.2 - support for
+ variable object properties re-added.
+
+
+1.3alpha1 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Made the TemplateObject available in CustomBlocks. Thanks to Felix Weis for
+ the patch.
+- Fixed issue #11228: Cannot supply an absolute Win32-Path to $t->process().
+- Better error message when an external (use) variable is not given.
+- Fixed issue #12289: String function str_last_index documented, but not
+ defined.
+- Fixed issue #12323: Using {cache_template} inside a block will now throw an
+ exception.
+- Fixed issue #11056:
+
+ * {dynamic} is only allowed after {cache_template} or in {cache_block}.
+ * corrected documentation in regard to TTL vs. ttl.
+ * fixed the cache file names in Windows (replace '\' with '-').
+
+- Fixed issue #12368: ezcTemplateConfiguration::addExtension() did not
+ check for invalid arguments correctly.
+- Implemented issue #10940: Possibility to set default permissions to
+ the compiled templates and generated cache files, by using the 0777 mode for
+ creating the directories in which the compiled templates are stored. This
+ mode is still modified by the umask.
+- Implemented issue #9973: Added a translation compiler to convert a string in
+ the original language, to the translated one without substituting parameters.
+ This is to have translation support for the Template component.
+- Added the capture() template block to collect a part of a template into a
+ variable.
+- Added support for variable length argument lists for custom functions
+ through the variableArgumentList element of the
+ ezcTemplateCustomFunctionDefinition.
+- Added support for additional parameters for custom blocks through the
+ excessParameters element of the ezcTemplateCustomBlockDefinition.
+
+
+TemplateTranslationTiein
+========================
+
+1.0 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.0rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.0beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.0alpha2 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12810: Issues with translations in templates (part c1): Quotes
+ incorrectly handled in string extractor.
+
+
+1.0alpha1 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Initial release of this package.
+
+Translation
+===========
+
+1.2 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.2rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.2beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Renamed the private ezcTranslation->callback_compile() and
+ ezcTranslation->parameter_callback_compile() to
+ ezcTranslation->callbackCompile() and
+ ezcTranslation->parameterCallbackCompile().
+
+
+1.2alpha2 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12810: Issues with translations in templates.
+
+
+1.2alpha1 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #9973: Added a translation compiler to convert a string in
+ the original language, to the translated one without substituting
+ parameters. This is to have translation support for the Template
+ component.
+- Implemented issue #10912: Add translation entries.
+- Added support for the new location element in Linguist version 1.1 files.
+- Added support for the keepObsolete option for the TsBackend. With this
+ enabled, the obsolete translations are not dropped while reading contexts.
+ This is both useful for testing as well as for manipulating TS files.
+
+
+
+Tree
+====
+
+1.1 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.1rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #13155: ezcTreeXmlInternalDataStore::fetchDataForNode fetches
+ data for wrong nodes.
+
+
+1.1beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12694: replace reflection test for class type with spl
+ function.
+
+
+1.1alpha1 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12139: Allow tree node keys to be auto-generated.
+- Fixed issue #12395: Tree docs lacking XHTML visualizaton.
+
+
+
+TreeDatabaseTiein
+=================
+
+1.1 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.1rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.1beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.1alpha1 - Monday 03 March 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12139: Allow tree node keys to be auto-generated.
+
+
+
+Url
+===
+
+1.2.1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12825: No longer throw a notice when an unordered parameter
+ name is empty.
+
+
+
+UserInput
+=========
+
+1.3 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.3beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3alpha1 - Tuesday 13 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12345: Added ezcInputForm::isValid().
+
+
+
+Workflow
+========
+
+1.2 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.2rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.2beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #12694: Replace reflection test for class type with SPL
+ function.
+
+
+1.2alpha1 - Monday 07 April 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #10941: Support for the Cancel Case workflow pattern. The
+ execution of a workflow can now be cancelled by either reaching a
+ ezcWorkflowNodeCancel node or by calling the cancel() node on an
+ ezcWorkflowExecution object. In either case, the execution is immediately
+ halted, already activated nodes will not be executed.
+- Implemented issue #12404: Separate file i/o from XML processing in
+ ezcWorkflowDefinitionStorageXml.
+- Implemented a plugin system that allows plugin developers to hook into
+ various extension points in the workflow execution engine.
+- The visualization visitor can now show the current state of a workflow in
+ execution. This includes the highlighting of activated nodes as well as the
+ contents of the workflow variables.
+- The new execution visualizer plugin (ezcWorkflowExecutionVisualizerPlugin)
+ uses the visualization visitor to create visualizations of each step of a
+ workflow execution.
+
+
+
+WorkflowDatabaseTiein
+=====================
+
+1.2 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.2rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes
+
+
+1.2beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Consistently use ezcDbHandler::quoteIdentifier() to quote SQL identifiers.
+
+
+1.2alpha1 - Monday 07 April 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented issue #10941: Support for the Cancel Case workflow pattern.
+- Fixed issue #12403: Multiple construction of
+ ezcWorkflowDatabaseDefinitionStorage.
+
+
+
diff --git a/include/ezcomponents/Graph/CREDITS b/include/ezcomponents/Graph/CREDITS
new file mode 100644
index 000000000..b4685afd0
--- /dev/null
+++ b/include/ezcomponents/Graph/CREDITS
@@ -0,0 +1,27 @@
+CREDITS
+=======
+
+eZ Components team
+------------------
+
+- Sergey Alexeev
+- Sebastian Bergmann
+- Jan Borsodi
+- Raymond Bosman
+- Frederik Holljen
+- Kore Nordmann
+- Derick Rethans
+- Vadym Savchuk
+- Tobias Schlitt
+- Alexandru Stanoi
+
+Contributors
+------------
+
+- Lars Jankowski
+
+ * ODO Meter Charts
+
+- Elger Thiele
+
+ * Custom highlight values in line and bar charts.
diff --git a/include/ezcomponents/Graph/ChangeLog b/include/ezcomponents/Graph/ChangeLog
new file mode 100644
index 000000000..d02db2b2d
--- /dev/null
+++ b/include/ezcomponents/Graph/ChangeLog
@@ -0,0 +1,228 @@
+1.3 - Monday 16 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3rc1 - Tuesday 10 June 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12581: Wrong axis captions in bar charts with manual min/max
+ values
+
+
+1.3beta1 - Tuesday 27 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- No changes.
+
+
+1.3alpha1 - Monday 05 May 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature #10829: Get resource from driver.
+- Implemented feature #10957: Embed glyphs for exact SVG font width
+ estimation.
+ (Patch by Freddie Witherden)
+- Implemented feature #11979: Line width configurable per data set.
+- Implemented feature #12382: Enhance line chart to allow invisible lines.
+- Fixed issue #12483: Average dataset does not work with a single value.
+
+
+1.2.1 - Monday 21 January 2008
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Implemented feature #9407: Cairo driver.
+- Fixed issue #11777: Optionally independent axis font configuration.
+- Fixed issue #12254: Bad property-check for strokeLineJoin in SVG driver.
+- Fixed issue #12295: Broken automatic scaling with manually set min value,
+ not divisible by major step.
+- Fixed issue #12326: Per datapoint colors not used with bar charts.
+- Fixed issue #12405: Highlightfontsize reduced when padding > 0 in highlight
+ font options.
+
+
+1.2 - Monday 17 December 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12238: Documentation error.
+- Fixed issue #12246: dataBorder doesn't works in 2D renderer for piecharts.
+
+
+1.2beta1 - Wednesday 28 November 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #12064: Gradients are not rendered correctly in Flash driver.
+- Implemented support for odometer charts. Thanks to Lars Jankowski from Oxid
+ esales for the initial patch.
+
+
+1.2alpha1 - Monday 29 October 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #11640: Polygon size reducement fails for very thin four edged
+ polygons.
+- Fixed issue #11511: Line thickness wasn't used for rendering.
+- Fixed issue #11509: Typo in line chart option.
+- Implemented feature #10978: Add support for stacked bar charts.
+- Implemented feature #11325: Allow values of 0 to be added to pie charts, to
+ be included in the legend and not rendered in the actual pie.
+- Implemented feature #11247: Custom highlight values in line and bar charts.
+ Thanks to Elger Thiele for the basic patch.
+- Implemented feature #10322 and #10565: Support for multiple x, y axis,
+ vertical and horizontal lines in line and bar charts.
+
+
+1.1.1 - Monday 13 August 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #11107: Floating point inaccuracies caused missing grid in line
+ chart.
+- Fixed issue #11157: Wrong display of single record with labeled axis.
+- Fixed issue #11180: Color Palette: $dataSetColor in ezcGraphPalette starts
+ with index 1 and not zero in pie charts.
+- Fixed issue #11207: Missing URL property for legend, or missing legend, may
+ cause PHP notice.
+- Fixed issue #11233: Path for circular arcs in SVG driver may break because
+ of locale settings.
+- Fixed issue #11235: In interactive 3D bar charts circle and bullet bars are
+ not linked.
+- Fixed issue #11236: Legend is not properly linked in interactive 3D line and
+ bar charts.
+
+
+1.1 - Monday 02 July 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Documentation updates.
+
+
+1.1rc1 - Monday 25 June 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #10828: PHP error when overwriting renderer in extended
+ ezGraphPieChart.
+- Documentation updates and fixes.
+
+
+1.1beta2 - Thursday 31 May 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #10741: Make ezcGraphOutOfLogithmicalBoundingsException more
+ descriptive.
+- Fixed issue #10744: ExactAxisLabelRenderer shows last value at position of
+ second last value if showLastValue is disabled.
+- Fixed issue #10745 (BaxedAxisLabelRenderer wrong label positions for angles:
+ (135 <= $angle <= 325).
+- Fixed issue #10746: Border size reducement algorithm fails for polygones
+ with edge lengths < reducement.
+- Fixed issue #10747: axisLabelRotatedRenderer accesses unitialised variables
+ for Angles % 90 != .0
+- Fixed issue #10750: SVG drivers output broken with wrong LC_NUMERIC.
+- Fixed issue #10759: Unset implementation broken in array access in datasets
+ and dataset properties.
+- Fixed issue #10830: Automatically shorten labels if not enough space is
+ available.
+- Fixed issue #10842: Pie charts fatal error with datasets with value sum <=
+ 0.
+- Fixed issue #10846: Division by zero in polygon size reducement algorithm
+ for edges with an angle equals 0 degree.
+- Fixed issue #10848: Missing pie segment labels in pie charts with 3d
+ renderer.
+- Fixed issue #10852: Fixed radar chart documentation.
+- Fixed issue #10858: Document on how to embed the SVG graphs in an HTML
+ document.
+- Fixed issue #10861: Circle sector size reducement failes for very big
+ angles.
+- Fixed issue #10881: Wrong labeling with ezcGraphDateAxis.
+
+
+1.1beta1 - Monday 07 May 2007
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Fixed issue #9916: Not documented: ArrayDataSet also can use Iterators.
+- Fixed issue #9926: Float values with date axis result in date parsing
+ exception.
+- Fixed issue #9948: Make ezcGraphPolynom documentation public.
+- Fixed issue #9950: Improved ezcGraphPolynom::__toString method for more
+ exact output.
+- Fixed issue #10018: Axis scale incorrect when startDate != first day of
+ month.
+- Fixed issue #10025: Wrong statement in tutorial about swf versions supported
+ by ext/ming.
+- Fixed issue #10074: Use iconv instead of mbstring.
+- Fixed issue #10055: Improve chart class documentation with chart elements.
+- Fixed issue #10056: Fixed drawing order for boxes with background and
+ border.
+- Fixed issue #10199: Use saveXML instead of save( 'php://output' ) to work
+ around PHP bug #40429.
+- Fixed issue #10246: sprintf output modification in PHP 5.2.1.
+- Fixed issue #10275: Low label count on labeled axis, when having (prime
+ number > 10) + 1 labels.
+- Fixed issue #10536: Bar side polygons are drawn at the wrong side.
+- Fixed issue #10599: Pie chart label formatting callback only accepts callback
+ functions but neither static nor non static methods.
+- Fixed issue #10606: Call to undefined function imagePsLoadFont() in gd tests
+ when no t1lib is available.
+- Fixed issue #10675: Arrow heads on axis too small.
+- Fixed issue #10693: ezcGraphChartElementAxis::getSteps returns minor steps
+ after last major steps.
+- Implemented feature #9402: Numeric datasets.
+- Implemented feature #9404: Add support for rotated texts on axis.
+- Implemented feature #9406: Add support for radar charts.
+- Implemented feature #9511: Added helper functions to create image maps an link SVG
+ images in ezcGraphTools.
+- Implemented feature #10017: Plot whole months on date axis respecting their
+ different length.
+- Implemented feature #10375: Manually reduce or increase the label count on
+ labeled axis.
+- Implemented feature #10470: Add support for format callback functions on all
+ axis.
+
+
+1.0 - Monday 18 December 2006
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Renamed ezcGraphMingDriver to ezcGraphFlashDriver.
+- Renamed pie chart options percentTreshHold to percentThreshold and
+ absoluteTreshHold to absoluteThreshold.
+- Added feature #9647: Added the renderToOutput() method to the drivers.
+- Fixed issue #9545: Pie chart treshhold does not work if aggregated data
+ stays below treshhold.
+- Fixed issue #9549: Pie chart slices are not contiguous with the flash driver.
+- Fixed issue #9568: Division by zero warning.
+- Fixed issue #9583: Data typecasting should only be done in axis.
+- Fixed issue #9586: No data rendered with string keys on date axis.
+- Fixed issue #9588: Wrong polynoms build from data.
+- Fixed issue #9612: Element links for SVG image in the legend require you to
+ click on exactly the text.
+- Fixed issue #9655: pieChartOffset and highlight do not work together in 2D
+ renderer.
+- Fixed issue #9762: Structs don't inherit from ezcBaseStruct.
+- Fixed issue #9764: 3D Pie chart segment side polygon in front of circular
+ arc.
+- Fixed issue #9795: Interferring bars, when bars count is higher then major
+ step count.
+- Fixed issue #9823: Failing tests with PHP 5.1, because of different
+ parameter handling in imagepng, and (string) not calling __toString method.
+- Fixed issue #9827: Use majorStep size for min/max estimating on numeric axis
+ with only one value.
+
+
+1.0beta2 - Monday 20 November 2006
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Added logarithmical scaled numeric axis.
+- Added treshhold for pie charts.
+- Added custom maximum value for pie charts.
+- Added ming driver for flash output.
+- Added text shadow, background and border to font options.
+- Added gleam to 2D pie charts.
+- Added feature #9304: Possibility to format labels via callback.
+- Fixed issue #9403: Element references do not work with Gleam in SVG.
+
+
+1.0beta1 - Monday 25 September 2006
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Initial release of this package.
diff --git a/include/ezcomponents/Graph/DESCRIPTION b/include/ezcomponents/Graph/DESCRIPTION
new file mode 100644
index 000000000..841497441
--- /dev/null
+++ b/include/ezcomponents/Graph/DESCRIPTION
@@ -0,0 +1 @@
+A component for creating pie charts, line graphs and other kinds of diagrams.
diff --git a/include/ezcomponents/Graph/design/class_diagram.png b/include/ezcomponents/Graph/design/class_diagram.png
new file mode 100644
index 000000000..fd2023580
Binary files /dev/null and b/include/ezcomponents/Graph/design/class_diagram.png differ
diff --git a/include/ezcomponents/Graph/design/design.txt b/include/ezcomponents/Graph/design/design.txt
new file mode 100644
index 000000000..eec244087
--- /dev/null
+++ b/include/ezcomponents/Graph/design/design.txt
@@ -0,0 +1,143 @@
+eZ component: Graph, Design
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+:Author: Kore Nordmann
+:Revision: $Revision: 3273 $
+:Date: $Date: 2006-08-15 11:42:17 +0200 (Tue, 15 Aug 2006) $
+
+Design Description
+==================
+
+Purpose of Graph package
+------------------------
+
+The Graph package will be used to generate different chart types from a user
+defined set of data. There will be 2D and 3D presentations for each chart
+type.
+
+Classes
+-------
+
+ezcGraph
+ Controller for the generated graphs. Offers factory methods for the other
+ classes, handles and dispatches the configuration and actions to the other
+ classes.
+
+ezcGraphDataset
+ Receives the user data, and stores the configuration for the dataset, like
+ color, label, etc. How the data is stored depends on the kind of the
+ dataset. The dataset will be extended for algorithms like averaging and
+ polynomial interpolation in the dataset.
+
+ezcGraphChart
+ Abstract class, which handles the global charts options like background
+ colors or images. Aggregates ezcGraphChartElement for configurable sub
+ elements.
+
+ezcGraphChartPie
+ Extends ezcGraphChart for pie charts. Offers additional options for pie
+ charts like tresh hold under which data is combined.
+
+ezcGraphChartLine
+ Extends ezcGraphChart for line charts. Additionally contains two objects
+ to represent and configure the axes.
+
+ezcGraphChartElement
+ Abstract class to define the interface how to access the configuration
+ directives of different chart elements like axes and legend.
+
+ezcGraphChartElementLegend
+ Offers configuration options for the charts legend like background color,
+ position and size.
+
+ezcGraphChartElementAxe
+ Offers the axes configuration options like scaling, lines within the
+ graph and labeling. Can do automagic scaling of the axes.
+
+ezcGraphRenderer
+ Abstract class which transforms the chart elements like pie segments,
+ bars, texts and lines to image primitives depending on the renderer.
+
+ezcGraphRenderer2D
+ Creates image primitives for the chart elements considered as two
+ dimensional.
+
+ezcGraphRenderer3D
+ Creates image primitives for the chart elements considered as three
+ dimensional.
+
+ezcGraphDriver
+ Offers methods to draw image primitives like textboxes, arcs, rectangles,
+ polygons and lines. Needs to be extended for each output format.
+
+ezcGraphDriverGD
+ Creates PNG images utilizing the GDlib bundled with PHP.
+
+Implementation
+==============
+
+ezcGraphManagager
+-----------------
+
+Offers factory methods to build up the wanted graph, containing a chart of a
+selected type, a renderer and a driver. Once aggregated the manager offers an
+unified interface to configure all parts of the graph.
+
+The manager can aggregate a finite count of datasets and forwards the to the
+chart. The chart builds the visual chart elements like pie segments, lines or
+bars, which are forwarded to the renderer. They are transformed to image
+primitives accoringly to the selected renderer.
+
+The datasets can be configured individually by the user of the package.
+
+API example
+-----------
+
+The following example shows how to use the class: ::
+
+ options->backgroundImage = 'background.png';
+ $pie->options->border->color = '#ff0000';
+ $pie->title = 'Apple Pie';
+
+ $pie->data['humanoids'] = new ezcGraphArrayDataSet(
+ array( 'monkey' => 54, 'ape' => 37, 'human' => 9 ) // adds a new data set
+ );
+ $pie->data['humanoids']->color['monkey'] = 'blueish'; // setting datapoint color
+ $pie->data['humanoids']->highlight( 'monkey' ); // chart type dependent
+
+
+ $line = new ezcGraphLineChart();
+ $line->options->backgroundColor = 'pink';
+
+ $line->data['income'] = new ezcGraphArrayDataSet(
+ array( 1990 => 5, 5.1, 5.4, 5.3, 6.9 )
+ );
+ $line->data['income']->color = 'blue';
+ $line->data['income']->symbol = ezcGraph::diamond;
+
+ $line->data['incomeWithTax'] = new ezcGraphArrayDataSet(
+ array( 1990 => 4.9, 5.0, 5.2, 5.1, 6.4 )
+ );
+ $line->data['incomeWithTax']->color = 'red';
+ $line->data['incomeWithTax']->symbol = ezcGraph::squareWithChupi;
+
+ // Create a new averaging line
+ $line->data['averageIncome'] = new ezcGraphAverageDatasSet( $line->data['income'] [, options] );
+
+ $line->renderer = new ezcGraphRenderer2D();
+ $line->driver = new ezcGraphGDDriver();
+
+ $line->render( 500, 200, 'file.png' );
+
+ ?>
+
+
+
+..
+ Local Variables:
+ mode: rst
+ fill-column: 79
+ End:
+ vim: et syn=rst tw=79
diff --git a/include/ezcomponents/Graph/design/enhancements/interactive_data_points.txt b/include/ezcomponents/Graph/design/enhancements/interactive_data_points.txt
new file mode 100644
index 000000000..c0db4a458
--- /dev/null
+++ b/include/ezcomponents/Graph/design/enhancements/interactive_data_points.txt
@@ -0,0 +1,167 @@
+eZ component: Graph: Interactive data points, Design
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Author: $Author: dr $
+:Revision: $Rev: 8393 $
+:Date: $Date: 2008-06-16 10:22:05 +0200 (Mon, 16 Jun 2008) $
+
+Introduction
+============
+
+Description
+-----------
+
+Interactive data points describe a feature in charts, that the viewer of the
+chart can interactively get more information about data, when viewing the
+chart, or moving his mouse pointer over points of interest in the chart.
+
+Requirements
+============
+
+There are two major sets of features to implement
+
+Value indication
+----------------
+
+The value indication means, that at the position of the mouse pointer lines
+are drawn, depeding on the chart type, to indicate the current data value at
+this poistion in the chart. In a line chart this would mean a horizontal and a
+vertical line to the axis and some coordinate information at the current
+position of the mouse pointer, while in radar chart a line to the center of
+the chart and an ellipse, indicating the value on the y axis, needs to be
+drawn.
+
+To enable this an option will be added to the driver option classes for the
+drivers, which are capeable of drawing this. The value indication will be off
+by default and can be enabled like this:
+
+::
+
+ $chart->driver->options->valueIndication = true;
+
+This option will be defined in the driver classes for the drivers implementing
+the ezcGraphDriverValueIndication interface. We cannot add those options to
+ezcGraphDriverOptions, because not all drivers will support this new feature,
+and implementing an interface in the driver option class makes no sense, as no
+methods, but only properties, will be added. The configure options will be
+delegated to an option class ezcGraphDriverValueIndicationOptions to have a
+central unique place to maintain those options.
+
+Driver support
+^^^^^^^^^^^^^^
+
+The driver itself needs to implement a new interface which defines the methods
+required to add the interactive elements to the resulting image. The renderer
+will call those methods on the driver if it implements the interface.
+
+::
+ interface ezcGraphDriverValueIndication
+ {
+ /***
+ * Add value indication for a cartesian coordinate system
+ *
+ * The graph data is rendered in the bounding box, the x values to
+ * indicate start with $xStart up to $xEnd, and the y values start
+ * with $yStart, up to $yEnd.
+ *
+ * http://en.wikipedia.org/wiki/Cartesian_coordinate
+ *
+ * @param ezcGraphBoundings $box
+ * @param float $xStart
+ * @param float $xEnd
+ * @param float $yStart
+ * @param float $yEnd
+ * return void
+ */
+ public function cartesianValueIndication(
+ ezcGraphBoundings $box,
+ $xStart,
+ $xEnd,
+ $yStart,
+ $yEnd
+ );
+
+ /***
+ * Add value indication for a polar coordinate system
+ *
+ * The graph data is rendered in the bounding box, the x values to
+ * indicate start with $xStart up to $xEnd, and the y values start
+ * with $yStart, up to $yEnd. The middle point of the polar coordinate
+ * system is always the center point of the bounding box. The zero
+ * angle may be rotated, depending on the graph rotation.
+ *
+ * http://en.wikipedia.org/wiki/Polar_coordinate
+ *
+ * @param ezcGraphBoundings $box
+ * @param float $xStart
+ * @param float $xEnd
+ * @param float $yStart
+ * @param float $yEnd
+ * return void
+ */
+ public function polarValueIndication(
+ ezcGraphBoundings $box,
+ $xStart,
+ $xEnd,
+ $yStart,
+ $yEnd
+ );
+ }
+
+Additional data point information
+---------------------------------
+
+When hovering or clicking on a data point or a legenda item, a box with
+additional information should be displayed. The box should contain text or
+user defined content.
+
+The data will be associated with the data point the same way we now associate
+URLs, by an additional option, so that you can optionally add information when
+creating your chart. This will consume nearly no memory if this feature is not
+used.
+
+::
+
+ $chart->data['data'] = new DataSet();
+ $chart->data['data']->informationBox['key'] = $object;
+
+You may of course set the informationBox property without specifying a special
+data set key to set it for all data points of a data set, even it may not make
+sense semantically.
+
+The value defined in the informationBox property will be passed to the driver.
+The driver classes do not need to implement additional public methods for
+this, but may optionally use an extended version of the ezcGraphContext
+structure, which is already used to pass semantical context of rendered image
+primitives to the driver. This struct will be extended by the additional
+optional property $informationBox.
+
+The type of $object cannot be checked before the graph is actually rendered,
+or the scripts for the data point information are created, because it
+rigorously depends on the driver, which values are accepted here.
+
+- The ming driver accepts SWFMovie, SWFSprite and SWFShape objects.
+- The SVG driver accepts XML, which should be valid SVG, which we won't check
+ for performance reasons.
+- The GD driver will use enhanced imagemaps with JavaScript, so it will accept
+ IDs of HTML elements of the document the image will be embedded in. The
+ element may contain arbitrary content and will get an absolut poistion and
+ moved in front of the chart image.
+
+The driver classes do not need to implement additional public methods to make
+use of the new informationBox property, but can just check the context struct,
+if it is available and render it properly then.
+
+The gd driver can of course not natively support this, because bitmaps may not
+contain any active content. An additional method createInteractiveImageMap()
+will be added to the ezcGraphTools class. This may be called, or the user can
+implement the JavaScript itself, to not infere with his own scripting
+mechanisms.
+
+
+..
+ Local Variables:
+ mode: rst
+ fill-column: 79
+ End:
+ vim: et syn=rst tw=79
diff --git a/include/ezcomponents/Graph/design/enhancements/multiple_axis.txt b/include/ezcomponents/Graph/design/enhancements/multiple_axis.txt
new file mode 100644
index 000000000..fc56fb34f
--- /dev/null
+++ b/include/ezcomponents/Graph/design/enhancements/multiple_axis.txt
@@ -0,0 +1,97 @@
+eZ component: Graph: Multiple axis, Design
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Author: $Author: kn $
+:Revision: $Revision: 5847 $
+:Date: $Date: 2007-08-08 12:26:43 +0200 (Wed, 08 Aug 2007) $
+
+Introduction
+============
+
+Description
+-----------
+
+Multiple axis are used in three different cases.
+
+- In stock chart, for example, they are used to show a different meaning of
+ the displayed data, for example the value of a single stock option and the
+ value sum of the complete company.
+
+- Another case multiple axis may be used, is displaying relating data in one
+ chart which has completely different scalings or units, like the used RAM
+ and the load on a machine.
+
+- A third case multiple axis could be used for, are named separators to
+ highlight data borders in your chart. In this case the step labels should be
+ at least optional.
+
+Requirements
+============
+
+From an implementation point of view the feature seperates into two different
+APIs we need to define.
+
+Adding additional axis
+^^^^^^^^^^^^^^^^^^^^^^
+
+When adding additional axis we want to support a finite number of marker axis,
+at every possible position in the chart.
+
+::
+
+ $marker = new ezcGraphChartElementLabeledAxis();
+ $marker->position = ezcGraph::LEFT;
+ $marker->chartPosition = .4;
+
+ $chart->additionalAxis[] = $marker;
+
+The property $position is already used by ezcGraphChartElement to indicate the
+overall position and accepts bitmasks of LEFT, RIGHT, BOTTOM and TOP. For Axis
+this indicates the base point of the axis and for additional axis it will
+define wheather the new axis is an X or Y axis in the cartesian coordinate
+system of bar and line charts.
+
+As the property $position is already used the property $chartPosition
+indicates the position of the axis in the charts data section. A value of 1
+will place the axis at the very end, and a value of 0 at the very beginning of
+the data.
+
+In the example above ezcGraph::LEFT means that the axis is drawn from the left
+to the right, so it is an additional x axis, and the $chartPosition indicates
+the position at 40% of the chart data bounding height.
+
+Adding data for additional axis
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Data may be added explicitely on one of the additional axis to use different
+scaling und units for this dataset. The axis values are received from the data
+sets when rendering the charts, so that we are able to define the usage of an
+additional axis at any point before rendering.
+
+::
+
+ $chart->data['foo'] = new ezcGraphArrayDataSet( ... );
+ $chart->data['foo']->xAxis = $marker; // See last example
+ $chart->data['foo']->yAxis = $chart->yAxis; // Redundant
+
+The assignement of additional axis is optional and if none was defined the
+original common chart axis will be used. You may define custom axis for one or
+both axis.
+
+Special consideration
+=====================
+
+You may of course define custom scaling, custom axis types and custom axis
+label rendering algorithms for each of the used axis.
+
+If no data set has been assigned to a axis it will not render any labels by
+using the ezcGraphAxisNoLabelRenderer. Otherwise the assigned data will be
+used the common way to calculate some default step sizes.
+
+
+..
+ Local Variables:
+ mode: rst
+ fill-column: 79
+ End:
+ vim: et syn=rst tw=79
diff --git a/include/ezcomponents/Graph/design/requirements.txt b/include/ezcomponents/Graph/design/requirements.txt
new file mode 100644
index 000000000..d22fe3c79
--- /dev/null
+++ b/include/ezcomponents/Graph/design/requirements.txt
@@ -0,0 +1,75 @@
+eZ component: Graph, Requirements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+:Author: Derick Rethans
+:Revision: $Revision: 2547 $
+:Date: $Date: 2006-04-12 11:53:25 +0200 (Wed, 12 Apr 2006) $
+
+Introduction
+============
+
+Description
+-----------
+
+The purpose of this component is to generate different kinds of diagrams from
+different kinds of data. The diagrams' appearance needs to be slick and
+customizable.
+
+Requirements
+============
+The first version of the component should cover the following types of
+charts:
+
+Piechart
+--------
+A diagram showing values as elements of a piechart. It should support:
+
+- highlighted parts (shown as a little bit outwards of the pie)
+- setting a tresh hold under which values are combined together in an "others"
+ part
+- automatic colours, but they should be able to set manually too
+- automatic legenda generation and label placement
+- 2D view, like:
+ http://www.eia.doe.gov/neic/brochure/gas04/images/pie%20chart.gif
+- 3D view, like: http://www.chambersfund.org/images/piechart.jpg
+- background image and/or colours
+
+Linechart
+---------
+A diagram with a variable y-axis showing values for a finite set of data
+points. The chart should be able to show different data sets, but all measured
+only in the same unit.
+
+Line charts should support:
+
+- automatic colours and styles, but each dataset should be able to be styled
+ manually
+- automatic scaling of the x and y-axis
+- automatic legenda generation
+- automatic label placement for both x and y-axis
+- options for setting when to draw vertical and horizontal lines, and in which
+ style they should be drawn
+- 2D view, like: http://www.swiftchart.com/line_ex5.jpg
+- 3D view, like: http://www.jspwiki.org/attach/LineChart/Line+Chart+3D.png
+- background image and/or colours
+
+Special Considerations
+======================
+
+The component should be flexible enough to allow averaging and other analysis
+methods to work on datasets and add rendered data to the resulting graph. This
+can for example be trend analysis or mean average etc.
+
+Formats
+=======
+
+Graphs should be rendered as PNGs in the first version, but it should be
+possible to have different renders to render to f.e. SVG or flash in later
+versions of the component.
+
+
+..
+ Local Variables:
+ mode: rst
+ fill-column: 79
+ End:
+ vim: et syn=rst tw=79
diff --git a/include/ezcomponents/Graph/design/requirements/interactive_data_points.txt b/include/ezcomponents/Graph/design/requirements/interactive_data_points.txt
new file mode 100644
index 000000000..f91c4c578
--- /dev/null
+++ b/include/ezcomponents/Graph/design/requirements/interactive_data_points.txt
@@ -0,0 +1,81 @@
+eZ component: Graph: Interactive data points, Requirements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Author: $Author: kn $
+:Revision: $Rev: 5711 $
+:Date: $Date: 2007-07-10 11:29:28 +0200 (Tue, 10 Jul 2007) $
+
+Introduction
+============
+
+Description
+-----------
+
+Interactive data points describe a feature in charts, that the viewer of the
+chart can interactively get more information about data, when viewing the
+chart, or moving his mouse pointer over points of interest in the chart.
+
+Requirements
+============
+
+There are two major sets of features to implement
+
+Value indication
+----------------
+
+The value indication means, that at the position of the mouse pointer lines
+are drawn, depeding on the chart type, to indicate the current data value at
+this poistion in the chart. In a line chart this would mean a horizontal and a
+vertical line to the axis and some coordinate information at the current
+position of the mouse pointer, while in radar chart a line to the center of
+the chart and an ellipse, indicating the value on the y axis, needs to be
+drawn.
+
+SVG
+ No real problem.
+
+GD / Cairo / IMagick
+ Not possible without large effort.
+
+Flash
+ No really big deal with flash and ext/ming.
+
+Additional data point information
+---------------------------------
+
+When hovering or clicking on a data point or a legenda ite, a box with
+additional information should be displayed. The box should contain text or
+user defined content.
+
+SVG
+ With only user defined inlined SVGs or Text in a box no big deal.
+
+GD / Cairo / IMagick
+ With a tool script generating HTML and javascript to use with the image
+ map, it should be possible to use HTML and text in boxes. This is similar
+ to the currently used mechanism to create image maps.
+
+Flash
+ Possible with user provided SWFMovies or shapes.
+
+Special consideration
+=====================
+
+It is impossible to implement natively more then simple text in a box for the
+additional information in highlighted data points, because this would require
+a complete redering model like HTML uses.
+
+Formats
+=======
+
+The integration of HTML, Flash or SVG documents should be possible, but would
+be a non driver generic mechanism. It seems not easily possible to convert
+user defined Flash, HTML and SVG to the respective other format.
+
+
+..
+ Local Variables:
+ mode: rst
+ fill-column: 79
+ End:
+ vim: et syn=rst tw=79
diff --git a/include/ezcomponents/Graph/design/requirements/multiple_axis.txt b/include/ezcomponents/Graph/design/requirements/multiple_axis.txt
new file mode 100644
index 000000000..8606f3d56
--- /dev/null
+++ b/include/ezcomponents/Graph/design/requirements/multiple_axis.txt
@@ -0,0 +1,60 @@
+eZ component: Graph: Multiple axis, Requirements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+:Author: $Author: kn $
+:Revision: $Rev: 5844 $
+:Date: $Date: 2007-08-08 11:00:39 +0200 (Wed, 08 Aug 2007) $
+
+Introduction
+============
+
+Description
+-----------
+
+Multiple axis are used in three different cases.
+
+- In stock chart, for example, they are used to show a different meaning of
+ the displayed data, for example the value of a single stock option and the
+ value sum of the complete company.
+
+- Another case multiple axis may be used, is displaying relating data in one
+ chart which has completely different scalings or units, like the used RAM
+ and the load on a machine.
+
+- A third case multiple axis could be used for, are named separators to
+ highlight data borders in your chart. In this case the step labels should be
+ at least optional.
+
+Requirements
+============
+
+To act as additional axis and seperators it is required, that additional axis
+can be placed at any position in the chart, especially at the very end and
+beginning of the charts data.
+
+It should be possible to associate a data set with a non default axis, to
+calculate the position of its data points based on a diffenrent scaling, then
+the default one.
+
+It should also be possible to add axis not depending on any data set and
+define the scaling manually. Those axis can be placed at any position in the
+chart, and if no scaling was explicitely given and no data set is associated,
+those axis will omit drawing steps or step labeling and just display a single
+line with the (optional) axis label.
+
+Special consideration
+=====================
+
+An API for assigning data sets to axis other then the default one will be
+defined in the design document. The calculation of the poistion of a data
+point in a chart is completely done in the chart classes, so it will be no
+problem to use another axis for this. No changes or additions in the renderers
+will be required.
+
+
+..
+ Local Variables:
+ mode: rst
+ fill-column: 79
+ End:
+ vim: et syn=rst tw=79
diff --git a/include/ezcomponents/Graph/docs/examples/ez_green.php b/include/ezcomponents/Graph/docs/examples/ez_green.php
new file mode 100644
index 000000000..b69884935
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/ez_green.php
@@ -0,0 +1,82 @@
+
diff --git a/include/ezcomponents/Graph/docs/examples/ez_red.php b/include/ezcomponents/Graph/docs/examples/ez_red.php
new file mode 100644
index 000000000..9e86a1627
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/ez_red.php
@@ -0,0 +1,82 @@
+
diff --git a/include/ezcomponents/Graph/docs/examples/forum_evolution.php b/include/ezcomponents/Graph/docs/examples/forum_evolution.php
new file mode 100644
index 000000000..94f2d9b94
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/forum_evolution.php
@@ -0,0 +1,48 @@
+palette = new ezcGraphPaletteEzBlue();
+$graph->xAxis->majorGrid = '#888888';
+$graph->yAxis->majorGrid = '#888888';
+
+// Add the data and hilight norwegian data set
+$graph->data['Posts'] = new ezcGraphArrayDataSet( array(
+ 'May 2006' => 1164,
+ 'Jun 2006' => 965,
+ 'Jul 2006' => 1014,
+ 'Aug 2006' => 1269,
+ 'Sep 2006' => 1269,
+ 'Oct 2006' => 771,
+) );
+
+$graph->data['per day'] = new ezcGraphArrayDataSet( array(
+ 'May 2006' => 38,
+ 'Jun 2006' => 32,
+ 'Jul 2006' => 33,
+ 'Aug 2006' => 41,
+ 'Sep 2006' => 34,
+ 'Oct 2006' => 25,
+) );
+
+// Set graph title
+$graph->title = 'Forum posts in last months';
+
+// Use 3d renderer, and beautify it
+$graph->renderer = new ezcGraphRenderer3d();
+
+$graph->renderer->options->barChartGleam = .5;
+$graph->renderer->options->legendSymbolGleam = .5;
+
+$graph->driver = new ezcGraphSvgDriver();
+
+// Output the graph with std SVG driver
+$graph->render( 500, 200, 'forum_evolution.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/examples/forum_month.php b/include/ezcomponents/Graph/docs/examples/forum_month.php
new file mode 100644
index 000000000..b0ca783af
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/forum_month.php
@@ -0,0 +1,47 @@
+palette = new ezcGraphPaletteEzRed();
+$graph->legend = false;
+
+// Add the data and hilight norwegian data set
+$graph->data['week'] = new ezcGraphArrayDataSet( array(
+ 'Claudia Kosny' => 128,
+ 'Kristof Coomans' => 70,
+ 'Xavier Dutoit' => 64,
+ 'David Jones' => 58,
+ 'Lukasz Serwatka' => 45,
+ 'Norman Leutner' => 22,
+ 'Marko Zmak' => 20,
+ 'sangib das' => 20,
+ 'Nabil Alimi' => 19,
+) );
+
+// Set graph title
+$graph->title = '10 most active users on forum in last month';
+
+// Use 3d renderer, and beautify it
+$graph->renderer = new ezcGraphRenderer3d();
+$graph->renderer->options->pieChartShadowSize = 12;
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+$graph->renderer->options->pieChartHeight = 16;
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->pieChartOffset = 100;
+
+$graph->driver = new ezcGraphSvgDriver();
+
+// Output the graph with std SVG driver
+$graph->render( 500, 200, 'forum_month.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/examples/forum_weekly.php b/include/ezcomponents/Graph/docs/examples/forum_weekly.php
new file mode 100644
index 000000000..df2a7a189
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/forum_weekly.php
@@ -0,0 +1,45 @@
+palette = new ezcGraphPaletteEzBlue();
+$graph->legend = false;
+
+// Add the data and hilight norwegian data set
+$graph->data['week'] = new ezcGraphArrayDataSet( array(
+ 'Claudia Kosny' => 45,
+ 'Lukasz Serwatka' => 35,
+ 'Kristof Coomans' => 25,
+ 'David Jones' => 23,
+ 'Xavier Dutoit' => 20,
+ 'sangib das' => 14,
+ 'Mark Marsiglio' => 10,
+ 'mark hayhurst' => 10,
+ 'Paul Borgermans' => 10,
+ 'Nabil Alimi' => 9,
+) );
+
+// Set graph title
+$graph->title = '10 most active users on forum in last week';
+
+// Use 3d renderer, and beautify it
+$graph->renderer = new ezcGraphRenderer3d();
+$graph->renderer->options->pieChartShadowSize = 12;
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+$graph->renderer->options->pieChartHeight = 16;
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->pieChartOffset = 100;
+
+$graph->driver = new ezcGraphSvgDriver();
+
+// Output the graph with std SVG driver
+$graph->render( 500, 200, 'forum_weekly.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/examples/forum_year.php b/include/ezcomponents/Graph/docs/examples/forum_year.php
new file mode 100644
index 000000000..38ea677b6
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/forum_year.php
@@ -0,0 +1,45 @@
+palette = new ezcGraphPaletteEzGreen();
+$graph->legend = false;
+
+// Add the data and hilight norwegian data set
+$graph->data['week'] = new ezcGraphArrayDataSet( array(
+ 'Lukasz Serwatka' => 1805,
+ 'Paul Forsyth' => 1491,
+ 'Paul Borgermans' => 1316,
+ 'Kristof Coomans' => 956,
+ 'Alex Jones' => 942 ,
+ 'Bard Farstad' => 941,
+ 'Tony Wood' => 900,
+) );
+
+// Set graph title
+$graph->title = 'Alltime 10 most active users on forum';
+
+// Use 3d renderer, and beautify it
+$graph->renderer = new ezcGraphRenderer3d();
+$graph->renderer->options->pieChartShadowSize = 12;
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+$graph->renderer->options->pieChartHeight = 16;
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->pieChartOffset = 100;
+
+$graph->driver = new ezcGraphSvgDriver();
+
+// Output the graph with std SVG driver
+$graph->render( 500, 200, 'forum_year.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/examples/php_magazine.php b/include/ezcomponents/Graph/docs/examples/php_magazine.php
new file mode 100644
index 000000000..ebf006674
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/php_magazine.php
@@ -0,0 +1,45 @@
+palette = new ezcGraphPaletteEzRed();
+
+// Add the data and hilight norwegian data set
+$graph->data['articles'] = new ezcGraphArrayDataSet( array(
+ 'English' => 1300000,
+ 'Germany' => 452000,
+ 'Netherlands' => 217000,
+ 'Norway' => 70000,
+) );
+$graph->data['articles']->highlight['Germany'] = true;
+
+// Set graph title
+$graph->title = 'Wikipedia articles by country';
+
+// Modify pie chart label to only show amount and percent
+$graph->options->label = '%2$d (%3$.1f%%)';
+
+// Use 3d renderer, and beautify it
+$graph->renderer = new ezcGraphRenderer3d();
+
+$graph->renderer->options->pieChartShadowSize = 12;
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+
+$graph->renderer->options->pieChartHeight = 8;
+$graph->renderer->options->pieChartRotation = .8;
+$graph->renderer->options->pieChartOffset = 190;
+
+$graph->renderer->options->legendSymbolGleam = .5;
+
+// Output the graph with std SVG driver
+$graph->render( 400, 200, 'wikipedia.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/examples/wikipedia.php b/include/ezcomponents/Graph/docs/examples/wikipedia.php
new file mode 100644
index 000000000..9a524394c
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/examples/wikipedia.php
@@ -0,0 +1,39 @@
+data['articles'] = new ezcGraphArrayDataSet( array(
+ 'English' => 1300000,
+ 'Germany' => 452000,
+ 'Netherlands' => 217000,
+ 'Norway' => 70000,
+) );
+$graph->data['articles']->highlight['Norway'] = true;
+
+// Set graph title
+$graph->title = 'Articles by country';
+
+// Modify pie chart label to only show amount and percent
+$graph->options->label = '%2$d (%3$.1f%%)';
+
+// Use 3d renderer, and beautify it
+$graph->renderer = new ezcGraphRenderer3d();
+$graph->renderer->options->pieChartShadowSize = 12;
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+$graph->renderer->options->pieChartHeight = 16;
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->pieChartOffset = 100;
+
+// Output the graph with std SVG driver
+$graph->render( 500, 200, 'wiki_graph.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_axis_datetime.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_axis_datetime.svg.png
new file mode 100644
index 000000000..cfc991600
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_axis_datetime.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_axis_labeled.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_axis_labeled.svg.png
new file mode 100644
index 000000000..73845f23d
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_axis_labeled.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_axis_numeric.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_axis_numeric.svg.png
new file mode 100644
index 000000000..e976c2314
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_axis_numeric.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_bar_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_bar_chart.svg.png
new file mode 100644
index 000000000..682bc6c76
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_bar_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_bar_chart_3d.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_bar_chart_3d.svg.png
new file mode 100644
index 000000000..fa8dab5a0
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_bar_chart_3d.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_bar_chart_options.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_bar_chart_options.svg.png
new file mode 100644
index 000000000..7963d3318
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_bar_chart_options.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_bar_line_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_bar_line_chart.svg.png
new file mode 100644
index 000000000..2dea0a5d3
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_bar_line_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_bar_options.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_bar_options.svg.png
new file mode 100644
index 000000000..27f8d668f
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_bar_options.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_chart_background.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_chart_background.svg.png
new file mode 100644
index 000000000..b087c4fd3
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_chart_background.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_chart_legend.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_chart_legend.svg.png
new file mode 100644
index 000000000..0b373c764
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_chart_legend.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_chart_title.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_chart_title.svg.png
new file mode 100644
index 000000000..1eec92f67
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_chart_title.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_complex_radar_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_complex_radar_chart.svg.png
new file mode 100644
index 000000000..bfd300577
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_complex_radar_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_custom_odometer_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_custom_odometer_chart.svg.png
new file mode 100644
index 000000000..454968954
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_custom_odometer_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_custom_palette.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_custom_palette.svg.png
new file mode 100644
index 000000000..5788f041d
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_custom_palette.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_dataset_average.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_dataset_average.svg.png
new file mode 100644
index 000000000..dd77b2bbe
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_dataset_average.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_dataset_numeric.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_dataset_numeric.svg.png
new file mode 100644
index 000000000..a25156ca5
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_dataset_numeric.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_driver_flash.swf b/include/ezcomponents/Graph/docs/img/tutorial_driver_flash.swf
new file mode 100644
index 000000000..3dbb2e010
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_driver_flash.swf differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_driver_gd.jpg b/include/ezcomponents/Graph/docs/img/tutorial_driver_gd.jpg
new file mode 100644
index 000000000..33e84d977
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_driver_gd.jpg differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_driver_svg.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_driver_svg.svg.png
new file mode 100644
index 000000000..04427d448
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_driver_svg.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_legend_options.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_legend_options.svg.png
new file mode 100644
index 000000000..8a7f0f429
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_legend_options.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_line_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_line_chart.svg.png
new file mode 100644
index 000000000..1eb57760c
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_line_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_line_chart_3d.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_line_chart_3d.svg.png
new file mode 100644
index 000000000..0e26bce58
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_line_chart_3d.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_line_chart_additional_axis.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_line_chart_additional_axis.svg.png
new file mode 100644
index 000000000..4a759e576
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_line_chart_additional_axis.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_line_chart_markers.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_line_chart_markers.svg.png
new file mode 100644
index 000000000..546fd7538
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_line_chart_markers.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_modified_palette.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_modified_palette.svg.png
new file mode 100644
index 000000000..0ecfc2300
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_modified_palette.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_odometer_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_odometer_chart.svg.png
new file mode 100644
index 000000000..17120c6f1
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_odometer_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_3d.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_3d.svg.png
new file mode 100644
index 000000000..086ea9d67
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_3d.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_options.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_options.svg.png
new file mode 100644
index 000000000..1909c32b1
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_options.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_pimped.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_pimped.svg.png
new file mode 100644
index 000000000..bd08a30ea
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_pie_chart_pimped.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_pie_options.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_pie_options.svg.png
new file mode 100644
index 000000000..e3515da7a
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_pie_options.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_radar_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_radar_chart.svg.png
new file mode 100644
index 000000000..49e4ccf1c
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_radar_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_reference_gd.html b/include/ezcomponents/Graph/docs/img/tutorial_reference_gd.html
new file mode 100644
index 000000000..e33e7950a
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/img/tutorial_reference_gd.html
@@ -0,0 +1,31 @@
+
+
+
+
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_reference_gd.png b/include/ezcomponents/Graph/docs/img/tutorial_reference_gd.png
new file mode 100644
index 000000000..4ecaa52b7
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_reference_gd.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_reference_svg.svg b/include/ezcomponents/Graph/docs/img/tutorial_reference_svg.svg
new file mode 100644
index 000000000..13197247c
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/img/tutorial_reference_svg.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_renderer_3d.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_renderer_3d.svg.png
new file mode 100644
index 000000000..3ee3b9588
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_renderer_3d.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_rotated_labels.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_rotated_labels.svg.png
new file mode 100644
index 000000000..a8d52f77b
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_rotated_labels.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_simple_pie.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_simple_pie.svg.png
new file mode 100644
index 000000000..c1cce9b42
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_simple_pie.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_stacked_bar_chart.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_stacked_bar_chart.svg.png
new file mode 100644
index 000000000..56f99f4ad
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_stacked_bar_chart.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/img/tutorial_user_palette.svg.png b/include/ezcomponents/Graph/docs/img/tutorial_user_palette.svg.png
new file mode 100644
index 000000000..49e33636c
Binary files /dev/null and b/include/ezcomponents/Graph/docs/img/tutorial_user_palette.svg.png differ
diff --git a/include/ezcomponents/Graph/docs/tutorial.txt b/include/ezcomponents/Graph/docs/tutorial.txt
new file mode 100644
index 000000000..5dc5338f1
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial.txt
@@ -0,0 +1,1223 @@
+=====================
+eZ Components - Graph
+=====================
+
+.. contents:: Table of Contents
+ :depth: 2
+
+Introduction
+============
+
+The Graph component enables you to create line, pie and bar charts. The
+output driver mechanism allows you to create different image types from each
+chart, and the available renderers make the chart output customizable from
+simple two-dimensional charts to beautiful three-dimensional data
+projections.
+
+ezcGraph separates different parts of the graph into chart elements, each
+representing one part of a graph. Elements include the title, the legend or an
+axis, and are all independently configurable. This design not only allows you to use
+different colors or fonts for each chart element, but also to define their
+position and size. The main chart elements are the same for all chart types.
+To define overall layouts for graphs, you can use palettes,
+which specify default colors, symbols, fonts and spacings.
+
+Data is provided through ezcGraphDataSet objects, which are normally created
+from simple arrays. You also can perform statistical operations on data, as you
+will see later.
+
+Class overview
+==============
+
+This section gives you an overview of the most important classes.
+
+ezcGraphChart
+ Line, bar and pie charts extend this abstract class, which represents the
+ chart to be rendered. ezcGraphChart collects the data and chart elements,
+ gives you access to all configuration settings and calls the driver and
+ renderer for creating the final output.
+
+ezcGraphDataSet
+ All datasets extend this abstract class to provide the data in a general
+ form accessible by the chart.
+
+ezcGraphChartElement
+ All chart elements store configuration values that define their layout. An
+ element's layout definition contains background, border, margin, padding and
+ font configuration. ezcGraphChartElement is extended by other classes for the
+ legend, text, background and different axis types.
+
+ezcGraphChartElementAxis
+ This class extends ezcGraphChartElement and is the base class for all axes.
+ There are different axis types for different data to be displayed, such as
+ numeric axis, string labeled axis or date axis.
+
+ezcGraphAxisLabelRenderer
+ This class defines the rendering algorithm for labels and grids on an axis.
+ The distinction between algorithms is necessary, because bar charts have
+ labels placed directly below the data point, but numerical data in line
+ charts should be placed next to the grid.
+
+ezcGraphPalette
+ ezcGraphPalette contains color, font, symbol and spacing definitions to be
+ applied to the entire graph.
+
+ezcGraphRenderer
+ This renderer transforms chart primitives, like pie chart segments, legend
+ or data lines, to image primitives. You have the choice between a two- and
+ three-dimensional renderer.
+
+ezcGraphDriver
+ This driver renders image primitives to an image. The default driver
+ will output as SVG, but you can also render JPEGs or PNGs using ext/gd.
+
+Chart types
+===========
+
+Pie charts
+----------
+
+The following is a simple example of how to create a pie chart using the
+default driver, palette and renderer.
+
+.. include:: tutorial/tutorial_simple_pie.php
+ :literal:
+
+Simply create a new chart object, optionally set a title for the chart,
+assign the data and render it. To assign data, the dataset container
+is accessed like an array to define an identifier for the dataset. The
+dataset in this example is created from an array, where the keys are used as
+the identifiers for the data points.
+
+Pie charts accept only one dataset, and the data point identifiers are used to
+create the legend. To generate the output, the default SVG renderer is used
+with the default 2D renderer. By default, the colors are applied from the Tango
+palette, from the Tango Desktop Project: http://tango.freedesktop.org/Tango_Desktop_Project
+
+Several dataset and data point presentation styles will be mentioned in this
+tutorial. One possibility is to highlight a special dataset or point. In line
+15, the data point Opera is highlighted; in the case of pie charts, this
+segment is pulled away from the center. See the renderer options class
+ezcGraphRendererOptions for more details.
+
+.. image:: img/tutorial_simple_pie.svg.png
+ :alt: Sample pie chart
+
+Pie chart options
+~~~~~~~~~~~~~~~~~
+
+There are several pie chart specific configuration options available. In
+eZ Components, options are always accessed via public properties. For a full
+list of all available options, see the documentation for the
+ezcGraphPieChartOptions class.
+
+.. include:: tutorial/tutorial_pie_options.php
+ :literal:
+
+In line 16, a sprintf format string is set, which defines how the labels are
+formatted. Instead of a sprintf format string, you could also set a callback
+function to do label formatting.
+
+In this example, we set a custom sum to force the pie chart to show the
+complete 100%. The percentThreshold lets the chart collect all data points that
+have less than the specified percentage to be aggregated in one data point. We
+also could define an absolute threshold, so that all data below a certain value
+would be aggregated in one data point. summarizeCaption defines the caption for
+this aggregated dataset.
+
+.. image:: img/tutorial_pie_options.svg.png
+ :alt: Pie chart configuration options
+
+Line charts
+-----------
+
+Line charts are created in the same way as pie charts, but they
+accept more than one dataset. We are using the default driver, palette and
+renderer in this example.
+
+.. include:: tutorial/tutorial_line_chart.php
+ :literal:
+
+There are only two differences compared to the last example. In line 6, we
+instantiate a ezcGraphLineChart object instead of ezcGraphPieChart and beginning in
+line 10, we assign multiple datasets from an array we included earlier in the
+script. The array in the file tutorial_wikipedia_data.php is built like this::
+
+ array(
+ 'Jan 2006' => 965,
+ 'Feb 2006' => 1000,
+ ...
+ ),
+ ...
+ );
+ ?>
+
+The result is a simple, default line chart.
+
+.. image:: img/tutorial_line_chart.svg.png
+ :alt: Simple line chart
+
+Bar charts
+----------
+
+Bar charts are very similar to line charts. They accept the same datasets and
+only define another default dataset display type and axis label renderer.
+
+.. include:: tutorial/tutorial_bar_chart.php
+ :literal:
+
+As you can see in line 6, we only change the chart constructor, and the
+other default values are applied.
+
+.. image:: img/tutorial_bar_chart.svg.png
+ :alt: Simple bar chart
+
+Lots of bars
+~~~~~~~~~~~~
+
+By default, ezcGraph reduces the amount of steps shown on an axis to about 10
+steps. This may cause unexpected results when trying to draw a bar chart
+with more than 10 bars in one dataset. You may override the behaviour by
+manually setting the amount of steps on the x axis::
+
+ xAxis->labelCount = count( $chart->data['name'] );
+ // Output graph ...
+ ?>
+
+This works because all datasets implement the Countable interface. If you want
+to use it for more than one dataset, you could do the following::
+
+ xAxis->labelCount = max(
+ count( $chart->data['name 1'] ),
+ count( $chart->data['name 2'] )
+ );
+ // Output graph ...
+ ?>
+
+Combining bar and line charts
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The only difference between bar and line charts is the display type of the
+dataset and the axis label renderer of the x-axis. You can use one of those
+constructors and modify your chart to display one or more datasets in
+either display type. The axis label renderer is described later in this
+tutorial.
+
+.. include:: tutorial/tutorial_bar_line_chart.php
+ :literal:
+
+After creating the datasets we modify one of the datasets in line 14 to change
+the default display type to ezcGraph::LINE. To more prominently display the
+line, we set one graph option in line 16. Options are accessed like public
+properties and in this case we set an option for the graph called "fillLines",
+which indicates what transparency value is used to fill the space underneath
+the line.
+
+.. image:: img/tutorial_bar_line_chart.svg.png
+ :alt: Combined bar and line chart
+
+More bar chart options
+~~~~~~~~~~~~~~~~~~~~~~
+
+There are some more options available for line and bar charts, which configure
+the highlighting of datasets.
+
+.. include:: tutorial/tutorial_bar_options.php
+ :literal:
+
+In line 20, the size of the highlight boxes is specified and lines 22 to
+24 change the font configuration for the highlight boxes. Highlighting
+works in much the same way as for pie charts, but in line and bar charts it
+makes sense to highlight a complete dataset instead of only one single data
+point. This is because there is usually more than one dataset in
+line and bar charts.
+
+.. image:: img/tutorial_bar_options.svg.png
+ :alt: Configured highlight in combined line and bar chart
+
+Stacked bar charts
+~~~~~~~~~~~~~~~~~~
+
+In stacked bar charts, the bars are not drawn next to each other, but
+aggregated in one bar, and the overall bar consumes the space of the sum of all
+single bars.
+
+.. include:: tutorial/tutorial_stacked_bar_chart.php
+ :literal:
+
+To use stacked bar charts, you only need to set the option $stackBars to "true".
+In the 3D renderer this will cause all bars to be rendered with the symbol
+ezcGraph::NO_SYMBOL.
+
+.. image:: img/tutorial_stacked_bar_chart.svg.png
+ :alt: Bar chart with stacked bars
+
+Radar charts
+------------
+
+Radar charts are very similar to line charts, but only with one axis, which
+will be drawn multiple times, rotated around the center point of the chart.
+
+.. include:: tutorial/tutorial_radar_chart.php
+ :literal:
+
+This again is one of the simplest ways to create a radar chart. Nearly all
+options described later are also available in radar charts. The basic
+difference is that an ezcGraphRadarChart object is created in line 6.
+Radar charts accept multiple datasets, like bar and line charts. In line 14
+the first element of the dataset is reassigned as the last element to close
+the circle. By not reassigning this value, you can get a radar chart where
+the tails do not join.
+
+.. image:: img/tutorial_radar_chart.svg.png
+ :alt: Simple radar chart
+
+Controlling radar axis
+~~~~~~~~~~~~~~~~~~~~~~
+
+Instead of having an x and a y axis, the radar chart has a main axis, which is
+the equivalent to the y axis in the other charts, and a rotation axis, the
+equivalent of the x axis. The steps on the rotation axis define the positions
+of the rotated main axis. This way you may use all available datasets and
+axes.
+
+.. include:: tutorial/tutorial_complex_radar_chart.php
+ :literal:
+
+The settings on the graph will be explained later in the tutorial in detail.
+In line 11 the type of the rotation axis is set to a numeric axis, which is
+explained in `Chart elements`_ -> `Axis`_.
+
+For line 15 to 23, a dataset is added with some random data. Using this
+data as a base, a new dataset is built, which calculates a polynomial
+interpolation function. This is described in more detail in the
+section `Datasets`_ -> `Average polynomial dataset`_. Lastly, the default colors
+and symbols from the palette are modified.
+
+.. image:: img/tutorial_complex_radar_chart.svg.png
+ :alt: Complex radar chart
+
+The resulting radar chart shows how minor steps on the rotation axis are drawn
+as a grayed out axis and major steps as a regular axis. Note that all
+types of datasets can be drawn using radar charts.
+
+Odometer charts
+---------------
+
+Odometer charts can display values on one bar with a gradient and markers,
+providing a nice way for the viewer to detect where a value is in a defined
+bounding.
+
+.. include:: tutorial/tutorial_odometer_chart.php
+ :literal:
+
+As you can see from the example, the odometer basically behaves like other
+chart types. First we create an object of the class ezcGraphOdometerChart, then
+a title and a dataset is assigned, as per usual. Similar to pie charts, an
+odometer only accepts one dataset. A legend does not exist for odometers - and
+you may of course assign an array dataset, containing only one element.
+
+.. image:: img/tutorial_odometer_chart.svg.png
+ :alt: Simple odometer
+
+The result is a bar, filled with the default gradient, with markers as
+indicators for the values on the bar. The axis span is automatically calculated
+for the provided values - you can modify them as usual, but take a look at the
+next example for this.
+
+Custom odometer chart
+~~~~~~~~~~~~~~~~~~~~~
+
+When using only one value on an odometer chart, you may wish to manually
+configure the span on the axis. You can do this as you normally would with
+any other axis.
+
+.. include:: tutorial/tutorial_custom_odometer_chart.php
+ :literal:
+
+In this example we only assign one value, so we get one marker on the
+odometer. The we start using the configuration options for odometers, defined in
+the ezcGraphOdometerChartOptions class.
+
+The start and end color define the colors used for the background gradient. The
+border options define the border, which is drawn around the chart gradient.
+After this you can configure the width of the markers, and the space
+used for the actual odometer.
+
+We then configure the minimum and maximum values for the axis and a label for
+the axis.
+
+.. image:: img/tutorial_custom_odometer_chart.svg.png
+ :alt: Custom configured odometer
+
+Palettes
+========
+
+ezcGraph offers graph palettes to define the overall style properties of
+chart elements. The style properties are similar to those from CSS:
+
+- color
+- background color
+- border color
+- border width
+- padding
+- margin
+- dataset symbols
+
+There are several predefined palettes in ezcGraph, but you can
+easily modify them or create your own palettes.
+
+Using a predefined palette
+--------------------------
+
+You can assign each class extending ezcGraphPalette to the palette
+property of your graph. You should do this before adding datasets, because the
+datasets request colors from the palette. If you set the palette after
+creating the datasets, the datasets will still use the colors from the
+default palette.
+
+.. include:: tutorial/tutorial_user_palette.php
+ :literal:
+
+The generated output differs quite a lot from the output using the default
+Tango palette. The colors for the background, datasets and fonts have been
+changed. Additionally, the palette sets a color for the major and minor grid, and
+defines a border width and color for the chart elements. The palette defaults
+to a sans-serif font and increases the margin between the chart elements.
+
+.. image:: img/tutorial_user_palette.svg.png
+ :alt: Combined bar / line chart with non default palette
+
+You can find a complete list of the available palettes in the class tree.
+
+Modifying a palette
+-------------------
+
+In the last example, we assigned a palette object to the palette property of
+the graph. You can of course create and modify the object before assigning it.
+
+.. include:: tutorial/tutorial_modified_palette.php
+ :literal:
+
+The palette object is created in line 6 and we overwrite some of its
+properties. An overview on all available properties can be found in the class
+documentation for the abstract class ezcGraphPalette. In this example we just
+set two colors for the automatic colorization of the datasets and three
+symbols for datasets.
+
+Since we assign more than two datasets, the first assigned color will be reused
+for the third dataset. You can see the usage of the symbols in the legend and
+on the line chart. The line chart displays a symbol for each data point if the
+symbol is set to something other than ezcGraph::NO_SYMBOL.
+
+.. image:: img/tutorial_modified_palette.svg.png
+ :alt: Combined bar / line chart with modified palette
+
+Creating a custom palette
+-------------------------
+
+To style the graphs to fit a custom look, such as a corporate identity, the
+easiest way is to create your own palette.
+To create a custom palette you can either extend one of the predefined palettes and
+overwrite their properties or extend the abstract palette class.
+
+.. include:: tutorial/tutorial_custom_palette_palette.php
+ :literal:
+
+Each undefined color will default to a transparent white. As you
+can see in the example definition, you can define alpha values beside the
+normal RGB values for the colors. After creating a custom palette, you can use
+it like any predefined palette, as previously explained.
+
+.. include:: tutorial/tutorial_custom_palette.php
+ :literal:
+
+The example now uses the custom palette to format the output. You can include
+palettes using your application's autoload mechanism or require them as
+shown in the example above.
+
+.. image:: img/tutorial_custom_palette.svg.png
+ :alt: Combined bar / line chart with custom palette
+
+Chart elements
+==============
+
+The chart elements all extend ezcGraphChartElement. Each of the elements can be
+configured independently. A default chart consists of
+the following elements:
+
+- title
+- background
+- legend
+- xAxis
+- yAxis
+
+The palette defines the default formatting of the elements. Not only can you set
+foreground and background colors for all the elements, but you can also
+define their position in the chart or prevent them from being rendered at
+all.
+
+Font configuration
+------------------
+
+We try to fulfill two goals regarding font configuration. First, there should
+be a single point to configure the fonts used for the text areas in the chart.
+On the other hand, it should be possible to configure the fonts independently
+for each chart element.
+
+The solution is that you can modify the global font configuration by
+accessing $graph->options->font. This takes effect on all chart elements
+unless you intentionally access the font configuration of an individual chart
+element. The following section shows an example of this.
+
+The chart title
+---------------
+
+The chart title element will only be rendered if you manually assign a title.
+It can be placed on top or at the bottom of the chart.
+
+.. include:: tutorial/tutorial_chart_title.php
+ :literal:
+
+The chart title is the simplest element. In line 9, we change the global font
+configuration to use a serif font. In the SVG renderer, only the font name is
+relevant, because it is up to the client to actually render the bitmap from the
+defined vector definitions.
+
+In line 11, we access the font configuration of the title element and change it
+back to use a sans-serif font. From now on, no change on the global font
+configuration will affect the title's font configuration. In line 14, we set a
+maximum font size, which now only affects the legend and the pie chart
+captions.
+
+Aside from the font configuration, we set an option for all chart
+elements in line 11 - the background color of the current element. This
+results in a gray background for the title element only.
+
+.. image:: img/tutorial_chart_title.svg.png
+ :alt: Font and title configuration in pie chart
+
+The background element
+----------------------
+
+With all drivers except the Ming (Flash) driver, you can set background images
+with the option to repeat them in the same way as in CSS definitions.
+
+.. include:: tutorial/tutorial_chart_background.php
+ :literal:
+
+In line 17, we set a background image, and define its position in line 18. You
+can use every combination of bottom / center / top with left / middle / right
+here, and it defaults to center | middle. In line 19, you set the type
+of repetition of the background image. This can be ezcGraph::NO_REPEAT or a
+combination of ezcGraph::HORIZONTAL and ezcGraph::VERTICAL. In this case, we
+just want a logo to be placed at the bottom right corner of the image.
+
+With the SVG driver, the image is inlined using a data URL with the base64
+encoded content of the binary image file. Using this driver, you do not need to
+worry about the locations of your referenced images.
+
+With the GD driver, super sampling is not applied to the images, as this would
+make them blurry.
+
+.. image:: img/tutorial_chart_background.svg.png
+ :alt: Pie chart with logo in background
+
+Of course, you could also apply the following settings to the background
+element: background color, borders, margins and padding.
+
+The legend
+----------
+
+The legend is shown by default and is automatically generated from the assigned
+data. If you want to disable the legend, you can do this by setting
+it to "false" (line 9).
+
+.. include:: tutorial/tutorial_chart_legend.php
+ :literal:
+
+.. image:: img/tutorial_chart_legend.svg.png
+ :alt: Pie chart without legend
+
+Legend configuration options
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Other than hiding the legend, you can also place it at the bottom, left or top
+in the chart; you can assign a title for the legend and change the symbol
+sizes; you can additionally set the legend size.
+
+.. include:: tutorial/tutorial_legend_options.php
+ :literal:
+
+To place the legend at another position on the graph, set the position
+property of the legend, as shown in line 17. If the legend is placed at the top or
+bottom, it will automatically use a landscape format. The space consumed by the
+legend is configured by the landscapeSize setting for landscape-oriented legends
+and the portraitSize setting otherwise. The assigned value is the percent
+portion of space taken up by the legend, relative to the size of the chart. The
+legend only displays a title if you manually set it, as shown in line 19.
+
+.. image:: img/tutorial_legend_options.svg.png
+ :alt: Legend configuration example
+
+Axis
+----
+
+The axis defines the unit scale in line and bar charts. There are always two
+axes - the x-axis and the y-axis, whose ranges are automatically
+received from the datasets and scaled to display appropriate values.
+
+There are different types of values to display for both the x-axis and the
+y-axis. ezcGraph supports different axis types for different types of data. For
+normal string keys, the standard labeled axis is usually the right choice. The
+numeric axis is predestined to display numeric data, and the date time axis is
+for data associated with dates or times. All of the axis types can be assigned
+to either axis.
+
+Labeled axes
+~~~~~~~~~~~~
+
+The labeled axis is default for the x-axis in both bar and line charts. It
+is intended to display string labels of datasets and uses the centered label
+renderer by default. You saw it in all the earlier examples with bar
+and line charts, but it can be used for both axes as well.
+
+.. include:: tutorial/tutorial_axis_labeled.php
+ :literal:
+
+You could argue whether such a chart is really useful - but it works. Instead of
+using numeric values, colors are assigned when creating the dataset. The
+labeled axis uses the values in the order they are assigned. Line 11 is
+the first time we actually configure the axis label renderer. The axis label
+renderer describes how the labels are placed on the axis - the labeled axis
+uses the centered axis label renderer by default, which places the labels
+centered next to the steps on the axis. The setting in line 11 forces the
+renderer to show the zero value, even though it interferes with the axis.
+
+.. image:: img/tutorial_axis_labeled.svg.png
+ :alt: Two labeled axes
+
+Numeric axis
+~~~~~~~~~~~~
+
+The numeric axis is the default for the y-axis. It displays
+numeric data and automatically determines appropriate scaling for the assigned
+values. However, you can also configure all scaling parameters manually.
+
+.. include:: tutorial/tutorial_axis_numeric.php
+ :literal:
+
+In this example, we force both axes to be numeric axes in line 10. In lines
+12 to 15, we manually set the scaling options for the x-axis. We do not set a
+minorStep size here, so it will be automatically calculated from the other
+values, as will the settings for the y-axis. Then, we create some random data
+and create two datasets from it as usual.
+
+.. image:: img/tutorial_axis_numeric.svg.png
+ :alt: Two numeric axes with random data
+
+The example shows one advantage of a numeric axis over a labeled axis for numeric
+data. The axes are moved away from the chart's border to display the negative
+values below and left of the axis.
+
+Date time axis
+~~~~~~~~~~~~~~
+
+Previously in this tutorial, we used a labeled axis for date time data on the
+x-axis in the Wikipedia examples. This works fine for evenly-distributed
+time spans. For other data, you should use the date time axis.
+
+.. include:: tutorial/tutorial_axis_datetime.php
+ :literal:
+
+You can use timestamps or date time strings as dataset keys. The strings will
+be converted using PHP's strtotime() function.
+
+.. image:: img/tutorial_axis_datetime.svg.png
+ :alt: Date axis example
+
+Axis label renderer
+~~~~~~~~~~~~~~~~~~~
+
+As mentioned earlier in this tutorial, the axis label renderer defines where a
+label is drawn relative to the step on the axis. You already saw examples
+for all available axis label renderers, but here is an overview:
+
+- ezcGraphAxisExactLabelRenderer
+
+ This is the default renderer for the numeric axis. The labels are drawn
+ directly below the axis step. This may look strange sometimes, because it
+ is not always possible to draw all labels of one axis on one side
+ of the step; the last or first label would exceed the available
+ space for the axis, and be rendered on the other side.
+
+- ezcGraphAxisCenteredLabelRenderer
+
+ This renderer is the default for the labeled axis in line charts and draws the
+ label centered next to the step. Therefore, this renderer omits the label for
+ the initial step on the axis (0, 0) by default. However, this can be forced
+ as shown in the example in `Axis`_ -> `Labeled axes`_. The label is omitted
+ because it would interfere with the axis or the labels of the other axis, and
+ thus be difficult to read.
+
+- ezcGraphAxisBoxedLabelRenderer
+
+ This is the default renderer for the labeled axis in bar charts. The steps
+ on the axis and the grid are not drawn at the position of the label, but
+ between two labels. This helps to recognize which bars belong together.
+ Labels are rendered centered between two steps on the axis.
+
+Rotated labels on axis
+~~~~~~~~~~~~~~~~~~~~~~
+
+There is one more new axis label renderer since version 1.1 of ezcGraph -
+ezcGraphAxisRotatedLabelRenderer, which enables you to render rotated labels on
+an axis.
+
+.. include:: tutorial/tutorial_rotated_labels.php
+ :literal:
+
+In line 9, a custom renderer is defined for the labeled x axis. You can
+assign custom axis label renderers on the property $axisLabelRenderer for
+ezcGraphChartElementAxis objects.
+
+The renderer used in this example has custom properties like the rotation of
+the labels, which is set in degrees, while the rotation direction depends on
+the direction of the axis.
+
+It makes sense to define more vertical space below the axis for the
+rotated labels as done in line 11 of the above example.
+
+.. image:: img/tutorial_rotated_labels.svg.png
+ :alt: Date axis example
+
+The results conatins rotated labels, which enables you to pack a lot more
+labels on one axis.
+
+Additional axis & markers
+-------------------------
+
+Aside from the x axis and the y axis, you can add additional axes and markers to one
+chart. You can also assign these additional axes to datasets, so that some
+datasets use different axes than others.
+
+Add markers to chart
+~~~~~~~~~~~~~~~~~~~~
+
+First, add some markers, which only get a label and reside at some
+user defined position, to a chart. You may use them to display data boundings
+on the y axis, or important values on the x axis.
+
+.. include:: tutorial/tutorial_line_chart_markers.php
+ :literal:
+
+You can see a standard line chart, like in the examples before, using the
+Wikipedia datasets. In line 15 we add another axis, and configure this one in
+the following lines. The position of an axis defines its origin. The
+position ezcGraph::LEFT means that the axis starts at the left side of the
+graph. You can also use ezcGraph::RIGHT to make the axis start on the right.
+
+The position of the axis may be defined by a float value, which defines the
+fractional position in the chart, calculated from the top left position. After
+this we also define a label for the axis.
+
+.. image:: img/tutorial_line_chart_markers.svg.png
+ :alt: Line chart with marker
+
+Additional axes
+~~~~~~~~~~~~~~~
+
+As previously noted, you can not only add additional axis to one chart, but you can
+also assign datasets to use one of these new axes. This will cause the
+axis to calculate its values depending on the assigned datasets and the data to
+orientate at the new axis.
+
+The new axis may also be of a completely different type than the original chart
+axis.
+
+.. include:: tutorial/tutorial_line_chart_additional_axis.php
+ :literal:
+
+In this chart we use a different axis, positioned at the very end of the chart,
+for the Norwegian Wikipedia data. Due to the different scaling for the English
+and Norwegian data, you can easily see that the development of articles is very
+similar, on a completely different level.
+
+To use the axis with some dataset, you need to assign the newly created axis to
+one of the existing axis properties and to the dataset. In this case we use the $yAxis
+property.
+
+.. image:: img/tutorial_line_chart_additional_axis.svg.png
+ :alt: Line chart with additional axis
+
+Datasets
+========
+
+Datasets receive user data and provide an interface for
+ezcGraph to access this data.
+
+Array data
+----------
+
+The array dataset was used for all examples until now, because it is the
+simplest way to provide data for ezcGraph.
+
+Average polynomial dataset
+--------------------------
+
+The average polynomial dataset uses an existing dataset with numeric keys and
+builds a polynomial that interpolates the data points in the given dataset using the
+least squares algorithm.
+
+.. include:: tutorial/tutorial_dataset_average.php
+ :literal:
+
+Here we use two numeric axes, because we only display numeric data in this
+example. First, we create a normal array dataset from some randomly generated
+data in line 14. We assign this dataset to the chart to see how well the
+polynomial fits the random data points.
+
+In line 20, we create a ezcGraphDataSetAveragePolynom dataset from the random
+data with a maximum polynomial degree of 3 (which is also the default
+value). You can directly access the polynomial when we add the dataset to the
+graph. The string representation of the polynomial itself is also used as the
+name of the dataset.
+
+.. image:: img/tutorial_dataset_average.svg.png
+ :alt: Average polynomial example
+
+For the computation of the polynomial, an equation has to be solved where the
+size of the matrix is equal to the point count of the used dataset. Be
+careful with datasets with large point counts. This could mean that
+ezcGraph will consume a lot of memory and processing power.
+
+Numeric dataset
+---------------
+
+Numeric datasets are used to represent mathematical functions in your chart.
+You can use callbacks to PHP functions, or your own functions or methods to
+define the mathematical function used to create the dataset.
+
+.. include:: tutorial/tutorial_dataset_numeric.php
+ :literal:
+
+The numeric dataset constructor receives the start and end values for
+the function's input and the function itself using PHP's `callback datatype`__. In
+this example we create a function on runtime using create_function(), which
+returns the name of the created function (which is a valid callback). The
+code of the created function in line 16 returns sine values for the input in
+degrees.
+
+__ http://php.net/manual/en/language.pseudo-types.php
+
+The resolution set in line 20 defines the number of steps used to interpolate
+the function in your graph. You should not use a number bigger than the width
+of your chart.
+
+.. image:: img/tutorial_dataset_numeric.svg.png
+ :alt: Example numeric dataset
+
+Renderer
+========
+
+The renderer transforms chart primitives into image primitives. For example, a
+pie segment including labels, highlighting and so on will be transformed
+into some text strings, circle sectors and symbols to link the text to the
+corresponding circle sector.
+
+ezcGraph comes with the default 2D renderer used in all of the above examples
+and a 3D renderer that renders the chart elements in a pseudo 3D isometric
+manner.
+
+2D renderer
+-----------
+
+All examples until now used the default 2D renderer. There are several
+renderer-specific options that have not yet been shown.
+
+Bar chart rendering options
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+All the options specific to bar charts are available for all
+current renderers.
+
+.. include:: tutorial/tutorial_bar_chart_options.php
+ :literal:
+
+As the 2D renderer is the default renderer, we do not need to specify it. In
+lines 28 and 29, we configure the width used for the bars. The
+option barMargin defines the distance between the sets of bars associated to
+one value. The barPadding setting in line 29 defines the distance between bars in one
+block.
+
+The option dataBorder in line 31 is available for all chart types in all
+renderers and defines the transparency used to draw darkened borders around
+bars or pie segments. In this case, we do not draw any borders.
+
+.. image:: img/tutorial_bar_chart_options.svg.png
+ :alt: Bar chart rendering options
+
+Pie chart rendering options
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. include:: tutorial/tutorial_pie_chart_options.php
+ :literal:
+
+One of the pie chart specific options is moveOut in line 21, which defines how
+much space in the pie chart is used to move the pie chart segments out from the
+center when highlighted. The pieChartOffset setting in line 23 defines the
+initial angle for the pie chart segments, which enables you to rotate the pie chart.
+
+In lines 25 to 27, a gleam on the pie chart is defined, with a transparency
+value in line 25 (which disables the gleam when set to 0), a color in line 26
+and the distance from the outer border of the pie chart in line 27.
+
+In line 29 and 30, a shadow with custom offset and color is added to the pie
+chart.
+
+.. image:: img/tutorial_pie_chart_options.svg.png
+ :alt: Pie chart rendering options
+
+Pimp my chart
+~~~~~~~~~~~~~
+
+.. include:: tutorial/tutorial_pie_chart_pimped.php
+ :literal:
+
+Aside from the gleam added in the last example, you can define a
+gleam for the legend symbols. In line 32, the transparency of the gleam is
+defined, followed by the size of the gleam. The gleam works for all symbol
+types except the circle, where a gleam is not appropriate. The size setting
+defines the size of the gleam as a percentage of the symbol size. In the last step in
+line 34, the gleam color is defined.
+
+.. image:: img/tutorial_pie_chart_pimped.svg.png
+ :alt: Pimped 2D pie chart
+
+3D renderer
+-----------
+
+The three-dimensional renderer can render all charts that the 2D
+renderer can do, and uses all the drivers that the 2D renderer uses. The only
+difference is that it generates isometric three-dimensional views on the data
+instead of simple two-dimensional views.
+
+.. include:: tutorial/tutorial_renderer_3d.php
+ :literal:
+
+This examples uses the same code as the first example, except for the renderer
+in line 17. You can use the 3D renderer with all of the above examples by
+adding this single line.
+
+.. image:: img/tutorial_renderer_3d.svg.png
+ :alt: Simple 3d pie chart
+
+3D pie charts
+~~~~~~~~~~~~~
+
+The options in the 2D renderer example still work, and we extend the example
+with some 3D renderer-specific options.
+
+.. include:: tutorial/tutorial_pie_chart_3d.php
+ :literal:
+
+The pieChartGleamBorder option was removed, because it looks a bit strange on
+3D pie charts, although it would still work. In lines 37 and 38, there are
+two new options, which configure the 3D effect of the pie chart. The first one
+defines the height of the pie and the second one defines the percent shrinkage
+compared to the maximum possible vertical size of the pie chart.
+
+.. image:: img/tutorial_pie_chart_3d.svg.png
+ :alt: Pimped 3D pie chart
+
+3D bar charts
+~~~~~~~~~~~~~
+
+3D bar charts use the symbol of the dataset as the basic shape for the
+rendered bar, so that you can render cylinders or cuboids in your charts.
+
+.. include:: tutorial/tutorial_bar_chart_3d.php
+ :literal:
+
+The symbols for these examples are set as described earlier in this tutorial.
+Two single options are set to improve the displayed image. legendSymbolGleam is
+activated with the default color, and barChartGleam is activated to get more
+beautiful bars. You could optionally darken the tops and sides of the bars using the
+options barDarkenSide and barDarkenTop.
+
+.. image:: img/tutorial_bar_chart_3d.svg.png
+ :alt: Pimped 3D bar chart
+
+3D line charts
+~~~~~~~~~~~~~~
+
+The line chart example with the 3D renderer is again quite simple. It reuses the
+example with the statistical data and the approximated polygon.
+
+.. include:: tutorial/tutorial_line_chart_3d.php
+ :literal:
+
+Again, the only thing that has changed is the use of the 3D renderer and the
+fillLines option (to show that it works for 3D charts as well).
+
+.. image:: img/tutorial_line_chart_3d.svg.png
+ :alt: 3D line chart example
+
+Drivers
+=======
+
+The driver gets the image primitives from the renderer and creates the final
+image. Different drivers can be used depending on
+the available extensions and the desired output format.
+
+There are some driver-specific options described below. You can also learn
+about them in the API documentation for each driver.
+
+SVG driver
+----------
+
+The default driver generates an SVG_ image, a standardized XML vector graphic
+format, which most of the modern browsers can display natively, except for
+Internet Explorer. Even with Internet Explorer, there are several
+plugins available from Corel [1]_, or Adobe [2]_, which enable the browser to
+render SVGs. There are several advantages in using the SVG driver. The XML
+documents can easily be modified later, and compressed effectively. The driver
+is very fast, and all shapes are displayed exactly and anti aliased. You can
+define templates, using an existing SVG document, where the generated chart is
+added to a dedicated group; you can then configure all rendering options of the SVG
+document. The example below shows such a template created with Inkscape_ and a
+simple pie chart rendered using this template.
+
+.. include:: tutorial/tutorial_driver_svg.php
+ :literal:
+
+.. image:: img/tutorial_driver_svg.svg.png
+ :alt: SVG driver example with template
+
+.. _SVG: http://www.w3.org/TR/SVG/
+.. _Inkscape: http://inkscape.org
+
+The only drawback of SVG is that it is impossible to determine or define the
+exact width of text strings. As a result, the driver can only estimate the size of
+text in the resulting image, which will sometimes fail slightly.
+
+.. [1] Abobe SVG plugin: http://www.adobe.com/svg/viewer/install/main.html
+.. [2] Corel SVG plugin: http://www.corel.com/servlet/Satellite?pagename=CorelCom/Layout&c=Content_C1&cid=1152796555406&lc=en
+
+Embedding SVG in HTML
+~~~~~~~~~~~~~~~~~~~~~
+
+If you want to embed SVGs in HTML there are several ways to do so. With XHTML
+you may inline the content of the SVG in your HTML, as both are just XML. You
+need to keep correct namespacing in mind here. Opera and Firefox will support
+this technique.
+
+Another way to reference SVGs in your HTML markup, is to use the object
+element like following example shows.
+
+::
+
+
+
+You can optionally specify the width and height as attributes in the options
+element. With this standards-conforming method, the drawback is that
+Microsoft Internet Explorer does not support this. With browsers from the
+Internet Explorer series, you can use the proprietary embed element.
+
+::
+
+
+
+You cannot specify an alternative text here, and this will not work with
+Opera and Firefox. If you need to support all browsers, you can use one of the
+common switches to distinguish between browsers.
+
+::
+
+
+
+
+
+
+Another alternative for embedding SVGs in your HTML would be to use iframes.
+
+GD driver
+---------
+
+The GD driver is, for now, the choice for generating bitmap images. It supports
+different font types, if available in your PHP installation, like True Type
+Fonts (using the FreeType 2 library or native TTF support) and PostScript
+Type 1 fonts. We use super sampling to enable basic anti aliasing in the GD
+driver, where the image is rendered twice as big and resized back to the
+requested size. This is used for all image primitives except text and images.
+
+There are some drawbacks in the GD library that we have not been able to
+overcome:
+
+- Transparent pie segments look very strange with GD
+- There is no native support for gradients in GD
+- Font anti aliasing depends on the font extension. Use the FreeType 2
+ library if available, which is the default behavior.
+
+There are some special configuration options for the GD driver. You can
+specify the super sampling rate used, and use different output formats (if
+available with your bundled GD extension) as shown in lines 13 to 15 in the
+following example.
+
+.. include:: tutorial/tutorial_driver_gd.php
+ :literal:
+
+.. image:: img/tutorial_driver_gd.jpg
+ :alt: GD driver example jpeg
+
+Ming/Flash driver
+-----------------
+
+ezcGraph can use ext/ming to generate Flash swf files. This driver only
+supports Palm Format Fonts (.fdb) and can only handle a very small subset of
+JPEGs as images for the chart background. On the other hand, Flash is a vector
+graphic format, so the images are rather small and can be compressed
+effectively. The font size estimation is exact and it support gradients and
+all of the used shapes. Ming does not support the generation of swf files using
+transparent backgrounds.
+
+.. include:: tutorial/tutorial_driver_flash.php
+ :literal:
+
+The Ming driver does not have a lot of available options. You need to use a
+valid font file, as in line 10, and you can set the
+compression rate used by the Ming driver to compress the resulting swf. The
+result is a `beautiful Flash image`__.
+
+__ img/tutorial_driver_flash.swf
+
+Element references
+==================
+
+Description
+-----------
+
+Element references describe a mechanism to modify and reference certain
+chart elements to add links, menus or other interactive features in your
+application. The type of the references depend on the driver you use to render
+charts. The GD driver will return points describing polygons, so that you can
+generate image maps from the data, while the SVG driver will return the IDs of
+XML elements.
+
+Element references are created in the renderer. This way it is also possible to
+reference legend symbols and text, data labels and of course the actual data
+elements.
+
+In ezcGraph version 1.1 and later, you can define URLs for datasets and data
+points that will be used when linking the elements. You now can use the function
+provided in ezcGraphTools to create the image maps and add links into SVG
+images.
+
+SVG example
+-----------
+
+.. include:: tutorial/tutorial_reference_svg.php
+ :literal:
+
+In ezcGraph version 1.1 and later, you can optionally set a custom cursor type used by the
+browser to indicate that you can click on a surface. The cursor defaults to a
+pointer normally used for links. You also need to assign
+URLs to the datasets or data points, as in the lines 17 and 18, and then
+call ezcGraphTools::linkSvgElements (line 23) to modify your SVG. The
+result will be a clickable `SVG image`__.
+
+__ img/tutorial_reference_svg.svg
+
+GD example
+----------
+
+In the case of GD we want to generate an image map instead of modifying the
+generated image. The driver returns polygons described by their edge
+coordinates, which you can use to generate an image map.
+
+.. include:: tutorial/tutorial_reference_gd.php
+ :literal:
+
+In line 20 we associate a URL to the complete dataset and in line 21 another
+URL for the Mozilla data point only. Those URLs will be used to create the
+image map in line 31. In the second parameter on line 31 we can
+provide a name for the image map, which should be used to associate the image
+map to the image in line 37. The result is a `linked legend and linked
+pie chart in your generated bitmap`__.
+
+__ img/tutorial_reference_gd.html
+
+SVG to bitmap conversion
+------------------------
+
+If you want to benefit from the more beautiful SVG output you can convert the
+vector graphics to a bitmap format, like PNG or JPEG, on the server side.
+There are several tools than can do this, but for each you need to be able
+to execute commands using the exec() function family.
+
+- librsvg
+ Small memory footprint and advanced SVG support, including filters (which
+ are not used by ezcGraph, though). Example command to convert an image:
+
+ ::
+
+ rsvg input.svg output.png
+
+- ImageMagick
+ Installed on most servers, but has some issues with transparent
+ backgrounds in SVG documents. Example command with ImageMagick:
+
+ ::
+
+ convert -background none input.svg output.png
+
+- Inkscape
+ Not available on most servers, but perfect for modifying SVG documents on
+ Unix and Windows. Requires X to install, but can be run on CLI, too:
+
+ ::
+
+ inkscape input.svg -e output.png
+
+All three converters can also export to PDF or postscript, and resize the
+document if desired. For further details, please read the documentation of the
+respective tool.
+
+Direct output
+=============
+
+By default, a graph is rendered to a file, as you normally want to cache
+generated images. ezcGraph also provides a method for the direct output of
+generated charts. Use this with caution.
+
+The ezcGraph::renderToOutput() method sends the correct Content-Type header
+for the selected output driver and writes the chart's image data directly to
+output. Do not output anything before using this method.
+
+.. include:: tutorial/tutorial_output.php
+ :literal:
+
+This example renders the first graph of this tutorial.
+
+More information
+================
+
+For more information, see the ezcGraph API documentation.
+
+
+..
+ Local Variables:
+ mode: rst
+ fill-column: 79
+ End:
+ vim: et syn=rst tw=79
diff --git a/include/ezcomponents/Graph/docs/tutorial/ez.png b/include/ezcomponents/Graph/docs/tutorial/ez.png
new file mode 100644
index 000000000..42f479964
Binary files /dev/null and b/include/ezcomponents/Graph/docs/tutorial/ez.png differ
diff --git a/include/ezcomponents/Graph/docs/tutorial/template.svg b/include/ezcomponents/Graph/docs/tutorial/template.svg
new file mode 100644
index 000000000..615689136
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/template.svg
@@ -0,0 +1,191 @@
+
+
+
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_autoload.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_autoload.php
new file mode 100644
index 000000000..8b197deaa
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_autoload.php
@@ -0,0 +1,20 @@
+
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_datetime.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_datetime.php
new file mode 100644
index 000000000..a638d20b4
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_datetime.php
@@ -0,0 +1,33 @@
+options->fillLines = 210;
+$graph->title = 'Concurrent requests';
+$graph->legend = false;
+
+$graph->xAxis = new ezcGraphChartElementDateAxis();
+
+// Add data
+$graph->data['Machine 1'] = new ezcGraphArrayDataSet( array(
+ '8:00' => 3241,
+ '8:13' => 934,
+ '8:24' => 1201,
+ '8:27' => 1752,
+ '8:51' => 123,
+) );
+$graph->data['Machine 2'] = new ezcGraphArrayDataSet( array(
+ '8:05' => 623,
+ '8:12' => 2103,
+ '8:33' => 543,
+ '8:43' => 2034,
+ '8:59' => 3410,
+) );
+
+$graph->data['Machine 1']->symbol = ezcGraph::BULLET;
+$graph->data['Machine 2']->symbol = ezcGraph::BULLET;
+
+$graph->render( 400, 150, 'tutorial_axis_datetime.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_labeled.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_labeled.php
new file mode 100644
index 000000000..10419d87e
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_labeled.php
@@ -0,0 +1,31 @@
+options->fillLines = 210;
+$graph->options->font->maxFontSize = 10;
+$graph->title = 'Error level colors';
+$graph->legend = false;
+
+$graph->yAxis = new ezcGraphChartElementLabeledAxis();
+$graph->yAxis->axisLabelRenderer->showZeroValue = true;
+
+$graph->yAxis->label = 'Color';
+$graph->xAxis->label = 'Error level';
+
+// Add data
+$graph->data['colors'] = new ezcGraphArrayDataSet(
+ array(
+ 'info' => 'blue',
+ 'notice' => 'green',
+ 'warning' => 'orange',
+ 'error' => 'red',
+ 'fatal' => 'red',
+ )
+);
+
+$graph->render( 400, 150, 'tutorial_axis_labeled.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_numeric.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_numeric.php
new file mode 100644
index 000000000..6dd55a67e
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_axis_numeric.php
@@ -0,0 +1,32 @@
+title = 'Some random data';
+$graph->legend = false;
+
+$graph->xAxis = new ezcGraphChartElementNumericAxis();
+
+$graph->xAxis->min = -15;
+$graph->xAxis->max = 15;
+$graph->xAxis->majorStep = 5;
+
+$data = array(
+ array(),
+ array()
+);
+for ( $i = -10; $i <= 10; $i++ )
+{
+ $data[0][$i] = mt_rand( -23, 59 );
+ $data[1][$i] = mt_rand( -23, 59 );
+}
+
+// Add data
+$graph->data['random blue'] = new ezcGraphArrayDataSet( $data[0] );
+$graph->data['random green'] = new ezcGraphArrayDataSet( $data[1] );
+
+$graph->render( 400, 150, 'tutorial_axis_numeric.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart.php
new file mode 100644
index 000000000..1fb8451a1
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart.php
@@ -0,0 +1,17 @@
+title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+
+$graph->render( 400, 150, 'tutorial_bar_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart_3d.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart_3d.php
new file mode 100644
index 000000000..61a01462a
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart_3d.php
@@ -0,0 +1,26 @@
+palette = new ezcGraphPaletteEz();
+$graph->title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['English']->symbol = ezcGraph::NO_SYMBOL;
+$graph->data['German']->symbol = ezcGraph::BULLET;
+$graph->data['Norwegian']->symbol = ezcGraph::DIAMOND;
+
+$graph->renderer = new ezcGraphRenderer3d();
+
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->barChartGleam = .5;
+
+$graph->render( 400, 150, 'tutorial_bar_chart_3d.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart_options.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart_options.php
new file mode 100644
index 000000000..cf035cf0d
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_chart_options.php
@@ -0,0 +1,35 @@
+title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['German']->displayType = ezcGraph::LINE;
+$graph->data['German']->highlight = true;
+$graph->data['German']->highlight['Mar 2006'] = false;
+
+$graph->options->fillLines = 210;
+
+$graph->options->highlightSize = 12;
+
+$graph->options->highlightFont->background = '#EEEEEC88';
+$graph->options->highlightFont->border = '#000000';
+$graph->options->highlightFont->borderWidth = 1;
+
+// $graph->renderer = new ezcGraphRenderer2d();
+
+$graph->renderer->options->barMargin = .2;
+$graph->renderer->options->barPadding = .2;
+
+$graph->renderer->options->dataBorder = 0;
+
+$graph->render( 400, 150, 'tutorial_bar_chart_options.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_line_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_line_chart.php
new file mode 100644
index 000000000..16451bbb4
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_line_chart.php
@@ -0,0 +1,20 @@
+title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['German']->displayType = ezcGraph::LINE;
+
+$graph->options->fillLines = 210;
+
+$graph->render( 400, 150, 'tutorial_bar_line_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_options.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_options.php
new file mode 100644
index 000000000..4dd846a7a
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_bar_options.php
@@ -0,0 +1,28 @@
+title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['German']->displayType = ezcGraph::LINE;
+$graph->data['German']->highlight = true;
+$graph->data['German']->highlight['Mar 2006'] = false;
+
+$graph->options->fillLines = 210;
+
+$graph->options->highlightSize = 12;
+
+$graph->options->highlightFont->background = '#EEEEEC88';
+$graph->options->highlightFont->border = '#000000';
+$graph->options->highlightFont->borderWidth = 1;
+
+$graph->render( 400, 150, 'tutorial_bar_options.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_background.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_background.php
new file mode 100644
index 000000000..f68863f92
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_background.php
@@ -0,0 +1,23 @@
+palette = new ezcGraphPaletteEzRed();
+$graph->title = 'Access statistics';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->background->image = 'ez.png';
+$graph->background->position = ezcGraph::BOTTOM | ezcGraph::RIGHT;
+$graph->background->repeat = ezcGraph::NO_REPEAT;
+
+$graph->render( 400, 150, 'tutorial_chart_background.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_legend.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_legend.php
new file mode 100644
index 000000000..791b6b600
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_legend.php
@@ -0,0 +1,21 @@
+palette = new ezcGraphPaletteEzGreen();
+$graph->title = 'Access statistics';
+
+$graph->legend = false;
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->render( 400, 150, 'tutorial_chart_legend.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_title.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_title.php
new file mode 100644
index 000000000..a8cb9c22a
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_chart_title.php
@@ -0,0 +1,26 @@
+palette = new ezcGraphPaletteEzBlue();
+$graph->title = 'Access statistics';
+
+$graph->options->font->name = 'serif';
+
+$graph->title->background = '#EEEEEC';
+$graph->title->font->name = 'sans-serif';
+
+$graph->options->font->maxFontSize = 8;
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->render( 400, 150, 'tutorial_chart_title.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_complex_radar_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_complex_radar_chart.php
new file mode 100644
index 000000000..e990cf946
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_complex_radar_chart.php
@@ -0,0 +1,32 @@
+palette = new ezcGraphPaletteEzBlue();
+$graph->options->fillLines = 220;
+$graph->legend->position = ezcGraph::BOTTOM;
+
+$graph->rotationAxis = new ezcGraphChartElementNumericAxis();
+$graph->rotationAxis->majorStep = 2;
+$graph->rotationAxis->minorStep = .5;
+
+mt_srand( 5 );
+$data = array();
+for ( $i = 0; $i <= 10; $i++ )
+{
+ $data[$i] = mt_rand( -5, 5 );
+}
+$data[$i - 1] = reset( $data );
+
+$graph->data['random data'] = $dataset = new ezcGraphArrayDataSet( $data );
+
+$average = new ezcGraphDataSetAveragePolynom( $dataset, 4 );
+$graph->data[(string) $average->getPolynom()] = $average;
+$graph->data[(string) $average->getPolynom()]->symbol = ezcGraph::NO_SYMBOL;
+$graph->data[(string) $average->getPolynom()]->color = '#9CAE86';
+
+$graph->render( 500, 250, 'tutorial_complex_radar_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_odometer_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_odometer_chart.php
new file mode 100644
index 000000000..cb0a6547b
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_odometer_chart.php
@@ -0,0 +1,36 @@
+title = 'Custom odometer';
+
+$graph->data['data'] = new ezcGraphArrayDataSet(
+ array( 87 )
+);
+
+// Set the marker color
+$graph->data['data']->color[0] = '#A0000055';
+
+// Set colors for the background gradient
+$graph->options->startColor = '#2E3436';
+$graph->options->endColor = '#EEEEEC';
+
+// Define a border for the odometer
+$graph->options->borderWidth = 2;
+$graph->options->borderColor = '#BABDB6';
+
+// Set marker width
+$graph->options->markerWidth = 5;
+
+// Set space, which the odometer may consume
+$graph->options->odometerHeight = .7;
+
+// Set axis span and label
+$graph->axis->min = 0;
+$graph->axis->max = 100;
+$graph->axis->label = 'Coverage ';
+
+$graph->render( 400, 150, 'tutorial_custom_odometer_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_palette.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_palette.php
new file mode 100644
index 000000000..179abee89
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_palette.php
@@ -0,0 +1,22 @@
+palette = new tutorialCustomPalette();
+$graph->title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['German']->displayType = ezcGraph::LINE;
+
+$graph->options->fillLines = 210;
+
+$graph->render( 400, 150, 'tutorial_custom_palette.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_palette_palette.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_palette_palette.php
new file mode 100644
index 000000000..13c6b63e9
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_custom_palette_palette.php
@@ -0,0 +1,24 @@
+
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_dataset_average.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_dataset_average.php
new file mode 100644
index 000000000..4d8134adb
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_dataset_average.php
@@ -0,0 +1,25 @@
+title = 'Some random data';
+$graph->legend->position = ezcGraph::BOTTOM;
+
+$graph->xAxis = new ezcGraphChartElementNumericAxis();
+
+$data = array();
+for ( $i = 0; $i <= 10; $i++ )
+{
+ $data[$i] = mt_rand( -5, 5 );
+}
+
+// Add data
+$graph->data['random data'] = $dataset = new ezcGraphArrayDataSet( $data );
+
+$average = new ezcGraphDataSetAveragePolynom( $dataset, 3 );
+$graph->data[(string) $average->getPolynom()] = $average;
+
+$graph->render( 400, 150, 'tutorial_dataset_average.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_dataset_numeric.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_dataset_numeric.php
new file mode 100644
index 000000000..1694cafc0
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_dataset_numeric.php
@@ -0,0 +1,24 @@
+title = 'Sinus';
+$graph->legend->position = ezcGraph::BOTTOM;
+
+$graph->xAxis = new ezcGraphChartElementNumericAxis();
+
+$graph->data['sinus'] = new ezcGraphNumericDataSet(
+ -360, // Start value
+ 360, // End value
+ create_function(
+ '$x',
+ 'return sin( deg2rad( $x ) );'
+ )
+);
+
+$graph->data['sinus']->resolution = 120;
+
+$graph->render( 400, 150, 'tutorial_dataset_numeric.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_cairo.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_cairo.php
new file mode 100644
index 000000000..37914fd34
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_cairo.php
@@ -0,0 +1,36 @@
+title = 'Access statistics';
+$graph->options->label = '%2$d (%3$.1f%%)';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+$graph->data['Access statistics']->highlight['Explorer'] = true;
+$graph->data['Access statistics']->symbol = ezcGraph::BULLET;
+
+$graph->renderer = new ezcGraphRenderer3d();
+
+$graph->renderer->options->pieChartShadowSize = 5;
+$graph->renderer->options->pieChartShadowColor = ezcGraphColor::create( '#000000' );
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+$graph->renderer->options->pieChartHeight = 8;
+$graph->renderer->options->pieChartSymbolColor = '#888A8588';
+$graph->renderer->options->pieChartRotation = .8;
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->legendSymbolGleamSize = .9;
+$graph->renderer->options->legendSymbolGleamColor = '#FFFFFF';
+
+$graph->driver = new ezcGraphCairoDriver();
+
+$graph->render( 400, 150, 'tutorial_driver_cairo.png' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_flash.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_flash.php
new file mode 100644
index 000000000..067e80124
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_flash.php
@@ -0,0 +1,31 @@
+title = 'Access statistics';
+$graph->legend = false;
+
+$graph->driver = new ezcGraphFlashDriver();
+$graph->options->font = 'tutorial_font.fdb';
+
+$graph->driver->options->compression = 7;
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->renderer = new ezcGraphRenderer3d();
+$graph->renderer->options->pieChartShadowSize = 10;
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+$graph->renderer->options->pieChartHeight = 16;
+$graph->renderer->options->legendSymbolGleam = .5;
+
+$graph->render( 400, 200, 'tutorial_driver_flash.swf' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_gd.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_gd.php
new file mode 100644
index 000000000..5aae23e61
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_gd.php
@@ -0,0 +1,33 @@
+palette = new ezcGraphPaletteEzGreen();
+$graph->title = 'Access statistics';
+$graph->legend = false;
+
+$graph->driver = new ezcGraphGdDriver();
+$graph->options->font = 'tutorial_font.ttf';
+
+// Generate a Jpeg with lower quality. The default settings result in a image
+// with better quality.
+//
+// The reduction of the supersampling to 1 will result in no anti aliasing of
+// the image. JPEG is not the optimal format for grapics, PNG is far better for
+// this kind of images.
+$graph->driver->options->supersampling = 1;
+$graph->driver->options->jpegQuality = 100;
+$graph->driver->options->imageFormat = IMG_JPEG;
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->render( 400, 200, 'tutorial_driver_gd.jpg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_svg.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_svg.php
new file mode 100644
index 000000000..6e9b96b90
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_driver_svg.php
@@ -0,0 +1,31 @@
+background->color = '#FFFFFFFF';
+$graph->title = 'Access statistics';
+$graph->legend = false;
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->renderer = new ezcGraphRenderer3d();
+$graph->renderer->options->pieChartShadowSize = 10;
+$graph->renderer->options->pieChartGleam = .5;
+$graph->renderer->options->dataBorder = false;
+$graph->renderer->options->pieChartHeight = 16;
+$graph->renderer->options->legendSymbolGleam = .5;
+
+$graph->driver->options->templateDocument = dirname( __FILE__ ) . '/template.svg';
+$graph->driver->options->graphOffset = new ezcGraphCoordinate( 25, 40 );
+$graph->driver->options->insertIntoGroup = 'ezcGraph';
+
+$graph->render( 400, 200, 'tutorial_driver_svg.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_font.fdb b/include/ezcomponents/Graph/docs/tutorial/tutorial_font.fdb
new file mode 100644
index 000000000..5fefdcb59
Binary files /dev/null and b/include/ezcomponents/Graph/docs/tutorial/tutorial_font.fdb differ
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_font.pfb b/include/ezcomponents/Graph/docs/tutorial/tutorial_font.pfb
new file mode 100644
index 000000000..130fac05b
Binary files /dev/null and b/include/ezcomponents/Graph/docs/tutorial/tutorial_font.pfb differ
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_font.ttf b/include/ezcomponents/Graph/docs/tutorial/tutorial_font.ttf
new file mode 100644
index 000000000..4b4ecc666
Binary files /dev/null and b/include/ezcomponents/Graph/docs/tutorial/tutorial_font.ttf differ
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_legend_options.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_legend_options.php
new file mode 100644
index 000000000..b12e040c0
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_legend_options.php
@@ -0,0 +1,23 @@
+palette = new ezcGraphPaletteEz();
+$graph->title = 'Access statistics';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->legend->position = ezcGraph::BOTTOM;
+$graph->legend->landscapeSize = .3;
+$graph->legend->title = 'Legend';
+
+$graph->render( 400, 150, 'tutorial_legend_options.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart.php
new file mode 100644
index 000000000..068e60476
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart.php
@@ -0,0 +1,17 @@
+title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+
+$graph->render( 400, 150, 'tutorial_line_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_3d.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_3d.php
new file mode 100644
index 000000000..bac6e2d36
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_3d.php
@@ -0,0 +1,28 @@
+title = 'Some random data';
+$graph->legend->position = ezcGraph::BOTTOM;
+$graph->options->fillLines = 210;
+
+$graph->xAxis = new ezcGraphChartElementNumericAxis();
+
+$data = array();
+for ( $i = 0; $i <= 10; $i++ )
+{
+ $data[$i] = mt_rand( -5, 5 );
+}
+
+// Add data
+$graph->data['random data'] = $dataset = new ezcGraphArrayDataSet( $data );
+
+$average = new ezcGraphDataSetAveragePolynom( $dataset, 3 );
+$graph->data[(string) $average->getPolynom()] = $average;
+
+$graph->renderer = new ezcGraphRenderer3d();
+
+$graph->render( 400, 150, 'tutorial_line_chart_3d.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_additional_axis.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_additional_axis.php
new file mode 100644
index 000000000..d06723ada
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_additional_axis.php
@@ -0,0 +1,33 @@
+title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+
+$graph->yAxis->min = 0;
+
+// Use a different axis for the norwegian dataset
+$graph->additionalAxis['norwegian'] = $nAxis = new ezcGraphChartElementNumericAxis();
+$nAxis->position = ezcGraph::BOTTOM;
+$nAxis->chartPosition = 1;
+$nAxis->min = 0;
+
+$graph->data['Norwegian']->yAxis = $nAxis;
+
+// Still use the marker
+$graph->additionalAxis['border'] = $marker = new ezcGraphChartElementNumericAxis();
+
+$marker->position = ezcGraph::LEFT;
+$marker->chartPosition = 1 / 3;
+
+$graph->render( 400, 150, 'tutorial_line_chart_additional_axis.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_markers.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_markers.php
new file mode 100644
index 000000000..a11637f8f
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_line_chart_markers.php
@@ -0,0 +1,23 @@
+title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+
+$graph->additionalAxis['border'] = $marker = new ezcGraphChartElementNumericAxis( );
+
+$marker->position = ezcGraph::LEFT;
+$marker->chartPosition = 1 / 3;
+$marker->label = 'One million!';
+
+$graph->render( 400, 150, 'tutorial_line_chart_markers.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_modified_palette.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_modified_palette.php
new file mode 100644
index 000000000..99a8ea73e
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_modified_palette.php
@@ -0,0 +1,22 @@
+palette = new tutorialCustomPalette();
+$graph->title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['German']->displayType = ezcGraph::LINE;
+
+$graph->options->fillLines = 210;
+
+$graph->render( 400, 150, 'tutorial_modified_palette.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_odometer_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_odometer_chart.php
new file mode 100644
index 000000000..c127811ad
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_odometer_chart.php
@@ -0,0 +1,16 @@
+title = 'Sample odometer';
+
+$graph->options->font->maxFontSize = 12;
+
+$graph->data['data'] = new ezcGraphArrayDataSet(
+ array( 1, 3, 9 )
+);
+
+$graph->render( 400, 150, 'tutorial_odometer_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_output.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_output.php
new file mode 100644
index 000000000..738c0e431
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_output.php
@@ -0,0 +1,19 @@
+title = 'Access statistics';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+$graph->data['Access statistics']->highlight['Opera'] = true;
+
+$graph->renderToOutput( 400, 150 );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_3d.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_3d.php
new file mode 100644
index 000000000..b5cdcaa42
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_3d.php
@@ -0,0 +1,42 @@
+palette = new ezcGraphPaletteEzRed();
+$graph->title = 'Access statistics';
+$graph->options->label = '%2$d (%3$.1f%%)';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+$graph->data['Access statistics']->highlight['Explorer'] = true;
+
+$graph->renderer = new ezcGraphRenderer3d();
+
+$graph->renderer->options->moveOut = .2;
+
+$graph->renderer->options->pieChartOffset = 63;
+
+$graph->renderer->options->pieChartGleam = .3;
+$graph->renderer->options->pieChartGleamColor = '#FFFFFF';
+
+$graph->renderer->options->pieChartShadowSize = 5;
+$graph->renderer->options->pieChartShadowColor = '#000000';
+
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->legendSymbolGleamSize = .9;
+$graph->renderer->options->legendSymbolGleamColor = '#FFFFFF';
+
+$graph->renderer->options->pieChartSymbolColor = '#55575388';
+
+$graph->renderer->options->pieChartHeight = 5;
+$graph->renderer->options->pieChartRotation = .8;
+
+$graph->render( 400, 150, 'tutorial_pie_chart_3d.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_options.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_options.php
new file mode 100644
index 000000000..7cf942ff0
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_options.php
@@ -0,0 +1,34 @@
+palette = new ezcGraphPaletteEzRed();
+$graph->title = 'Access statistics';
+$graph->legend = false;
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+$graph->data['Access statistics']->highlight['Explorer'] = true;
+
+// $graph->renderer = new ezcGraphRenderer2d();
+
+$graph->renderer->options->moveOut = .2;
+
+$graph->renderer->options->pieChartOffset = 63;
+
+$graph->renderer->options->pieChartGleam = .3;
+$graph->renderer->options->pieChartGleamColor = '#FFFFFF';
+$graph->renderer->options->pieChartGleamBorder = 2;
+
+$graph->renderer->options->pieChartShadowSize = 5;
+$graph->renderer->options->pieChartShadowColor = '#BABDB6';
+
+$graph->render( 400, 150, 'tutorial_pie_chart_options.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_pimped.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_pimped.php
new file mode 100644
index 000000000..204e1aa57
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_chart_pimped.php
@@ -0,0 +1,40 @@
+palette = new ezcGraphPaletteBlack();
+$graph->title = 'Access statistics';
+$graph->options->label = '%2$d (%3$.1f%%)';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+$graph->data['Access statistics']->highlight['Explorer'] = true;
+
+// $graph->renderer = new ezcGraphRenderer2d();
+
+$graph->renderer->options->moveOut = .2;
+
+$graph->renderer->options->pieChartOffset = 63;
+
+$graph->renderer->options->pieChartGleam = .3;
+$graph->renderer->options->pieChartGleamColor = '#FFFFFF';
+$graph->renderer->options->pieChartGleamBorder = 2;
+
+$graph->renderer->options->pieChartShadowSize = 3;
+$graph->renderer->options->pieChartShadowColor = '#000000';
+
+$graph->renderer->options->legendSymbolGleam = .5;
+$graph->renderer->options->legendSymbolGleamSize = .9;
+$graph->renderer->options->legendSymbolGleamColor = '#FFFFFF';
+
+$graph->renderer->options->pieChartSymbolColor = '#BABDB688';
+
+$graph->render( 400, 150, 'tutorial_pie_chart_pimped.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_options.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_options.php
new file mode 100644
index 000000000..be8911f03
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_pie_options.php
@@ -0,0 +1,25 @@
+title = 'Elections 2005 Germany';
+
+$graph->data['2005'] = new ezcGraphArrayDataSet( array(
+ 'CDU' => 35.2,
+ 'SPD' => 34.2,
+ 'FDP' => 9.8,
+ 'Die Gruenen' => 8.1,
+ 'PDS' => 8.7,
+ 'NDP' => 1.6,
+ 'REP' => 0.6,
+) );
+
+$graph->options->label = '%3$.1f%%';
+$graph->options->sum = 100;
+$graph->options->percentThreshold = 0.02;
+$graph->options->summarizeCaption = 'Others';
+
+$graph->render( 400, 150, 'tutorial_pie_options.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_radar_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_radar_chart.php
new file mode 100644
index 000000000..d10a1d705
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_radar_chart.php
@@ -0,0 +1,19 @@
+title = 'Wikipedia articles';
+$graph->options->fillLines = 220;
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+ $graph->data[$language][] = reset( $data );
+}
+
+$graph->render( 400, 150, 'tutorial_radar_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_reference_gd.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_reference_gd.php
new file mode 100644
index 000000000..46389f83e
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_reference_gd.php
@@ -0,0 +1,41 @@
+palette = new ezcGraphPaletteEzGreen();
+$graph->title = 'Access statistics';
+
+$graph->driver = new ezcGraphGdDriver();
+$graph->options->font = 'tutorial_font.ttf';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->data['Access statistics']->url = 'http://example.org/';
+$graph->data['Access statistics']->url['Mozilla'] = 'http://example.org/mozilla';
+
+$graph->render( 400, 200, 'tutorial_reference_gd.png' );
+
+?>
+
+
+
+
+
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_reference_svg.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_reference_svg.php
new file mode 100644
index 000000000..61f18c1c8
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_reference_svg.php
@@ -0,0 +1,25 @@
+palette = new ezcGraphPaletteEz();
+$graph->title = 'Access statistics';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+
+$graph->data['Access statistics']->url = 'http://example.org/';
+$graph->data['Access statistics']->url['Mozilla'] = 'http://example.org/mozilla';
+
+$graph->render( 400, 200, 'tutorial_reference_svg.svg' );
+
+$graph->driver->options->linkCursor = 'crosshair';
+ezcGraphTools::linkSvgElements( $graph );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_renderer_3d.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_renderer_3d.php
new file mode 100644
index 000000000..0d2d178fe
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_renderer_3d.php
@@ -0,0 +1,21 @@
+title = 'Access statistics';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+$graph->data['Access statistics']->highlight['Opera'] = true;
+
+$graph->renderer = new ezcGraphRenderer3d();
+
+$graph->render( 400, 150, 'tutorial_renderer_3d.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_rotated_labels.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_rotated_labels.php
new file mode 100644
index 000000000..98aba886b
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_rotated_labels.php
@@ -0,0 +1,24 @@
+title = 'Wikipedia articles';
+
+$graph->xAxis->axisLabelRenderer = new ezcGraphAxisRotatedLabelRenderer();
+$graph->xAxis->axisLabelRenderer->angle = 45;
+$graph->xAxis->axisSpace = .2;
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['German']->displayType = ezcGraph::LINE;
+
+$graph->options->fillLines = 210;
+
+$graph->render( 400, 150, 'tutorial_rotated_labels.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_simple_pie.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_simple_pie.php
new file mode 100644
index 000000000..db2ca5ed7
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_simple_pie.php
@@ -0,0 +1,19 @@
+title = 'Access statistics';
+
+$graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ 'Mozilla' => 19113,
+ 'Explorer' => 10917,
+ 'Opera' => 1464,
+ 'Safari' => 652,
+ 'Konqueror' => 474,
+) );
+$graph->data['Access statistics']->highlight['Opera'] = true;
+
+$graph->render( 400, 150, 'tutorial_simple_pie.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_stacked_bar_chart.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_stacked_bar_chart.php
new file mode 100644
index 000000000..96f9c6830
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_stacked_bar_chart.php
@@ -0,0 +1,22 @@
+title = 'Wikipedia articles';
+
+// Stack bars
+$graph->options->stackBars = true;
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+
+$graph->yAxis->label = 'Thousand articles';
+
+$graph->render( 400, 150, 'tutorial_stacked_bar_chart.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_user_palette.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_user_palette.php
new file mode 100644
index 000000000..507acc3c6
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_user_palette.php
@@ -0,0 +1,21 @@
+palette = new ezcGraphPaletteBlack();
+$graph->title = 'Wikipedia articles';
+
+// Add data
+foreach ( $wikidata as $language => $data )
+{
+ $graph->data[$language] = new ezcGraphArrayDataSet( $data );
+}
+$graph->data['German']->displayType = ezcGraph::LINE;
+
+$graph->options->fillLines = 210;
+
+$graph->render( 400, 150, 'tutorial_user_palette.svg' );
+
+?>
diff --git a/include/ezcomponents/Graph/docs/tutorial/tutorial_wikipedia_data.php b/include/ezcomponents/Graph/docs/tutorial/tutorial_wikipedia_data.php
new file mode 100644
index 000000000..f4397bd2a
--- /dev/null
+++ b/include/ezcomponents/Graph/docs/tutorial/tutorial_wikipedia_data.php
@@ -0,0 +1,30 @@
+ array(
+ 'Jan 2006' => 965,
+ 'Feb 2006' => 1000,
+ 'Mar 2006' => 1100,
+ 'Apr 2006' => 1100,
+ 'May 2006' => 1200,
+ 'Jun 2006' => 1300,
+ ),
+ 'German' => array(
+ 'Jan 2006' => 357,
+ 'Feb 2006' => 371,
+ 'Mar 2006' => 387,
+ 'Apr 2006' => 402,
+ 'May 2006' => 429,
+ 'Jun 2006' => 435,
+ ),
+ 'Norwegian' => array(
+ 'Jan 2006' => 49,
+ 'Feb 2006' => 52,
+ 'Mar 2006' => 56,
+ 'Apr 2006' => 59,
+ 'May 2006' => 63,
+ 'Jun 2006' => 67,
+ ),
+);
+
+?>
diff --git a/include/ezcomponents/Graph/src/axis/container.php b/include/ezcomponents/Graph/src/axis/container.php
new file mode 100644
index 000000000..e2d097ff3
--- /dev/null
+++ b/include/ezcomponents/Graph/src/axis/container.php
@@ -0,0 +1,221 @@
+chart = $chart;
+ }
+
+ /**
+ * Returns if the given offset exists.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key Identifier of dataset.
+ * @return bool True when the offset exists, otherwise false.
+ */
+ public function offsetExists( $key )
+ {
+ return isset( $this->data[$key] );
+ }
+
+ /**
+ * Returns the element with the given offset.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key Identifier of dataset.
+ * @return ezcGraphChartElementAxis
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If no dataset with identifier exists
+ */
+ public function offsetGet( $key )
+ {
+ if ( !isset( $this->data[$key] ) )
+ {
+ throw new ezcBasePropertyNotFoundException( $key );
+ }
+
+ return $this->data[$key];
+ }
+
+ /**
+ * Set the element with the given offset.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key
+ * @param ezcGraphChartElementAxis $value
+ * @return void
+ *
+ * @throws ezcBaseValueException
+ * If supplied value is not an ezcGraphChartElementAxis
+ */
+ public function offsetSet( $key, $value )
+ {
+ if ( !$value instanceof ezcGraphChartElementAxis )
+ {
+ throw new ezcBaseValueException( $key, $value, 'ezcGraphChartElementAxis' );
+ }
+
+ if ( $key === null )
+ {
+ $key = count( $this->data );
+ }
+
+ // Add axis and configure it with current font and palette
+ $this->data[$key] = $value;
+ $value->font = $this->chart->options->font;
+ $value->setFromPalette( $this->chart->palette );
+
+ return $value;
+ }
+
+ /**
+ * Unset the element with the given offset.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key
+ * @return void
+ */
+ public function offsetUnset( $key )
+ {
+ if ( !isset( $this->data[$key] ) )
+ {
+ throw new ezcBasePropertyNotFoundException( $key );
+ }
+
+ unset( $this->data[$key] );
+ }
+
+ /**
+ * Returns the currently selected dataset.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return ezcGraphChartElementAxis The currently selected dataset.
+ */
+ public function current()
+ {
+ return current( $this->data );
+ }
+
+ /**
+ * Returns the next dataset and selects it or false on the last dataset.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return mixed ezcGraphChartElementAxis if the next dataset exists, or false.
+ */
+ public function next()
+ {
+ return next( $this->data );
+ }
+
+ /**
+ * Returns the key of the currently selected dataset.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return int The key of the currently selected dataset.
+ */
+ public function key()
+ {
+ return key( $this->data );
+ }
+
+ /**
+ * Returns if the current dataset is valid.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return bool If the current dataset is valid
+ */
+ public function valid()
+ {
+ return ( current( $this->data ) !== false );
+ }
+
+ /**
+ * Selects the very first dataset and returns it.
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return ezcGraphChartElementAxis The very first dataset.
+ */
+ public function rewind()
+ {
+ return reset( $this->data );
+ }
+
+ /**
+ * Returns the number of datasets in the row.
+ *
+ * This method is part of the Countable interface to allow the usage of
+ * PHP's count() function to check how many datasets exist.
+ *
+ * @return int Number of datasets.
+ */
+ public function count()
+ {
+ return count( $this->data );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/axis/date.php b/include/ezcomponents/Graph/src/axis/date.php
new file mode 100644
index 000000000..d64d48256
--- /dev/null
+++ b/include/ezcomponents/Graph/src/axis/date.php
@@ -0,0 +1,585 @@
+ 'H:i.s',
+ // Ten seconds
+ 10 => 'H:i.s',
+ // Thirty seconds
+ 30 => 'H:i.s',
+ // Minute
+ 60 => 'H:i',
+ // Ten minutes
+ 600 => 'H:i',
+ // Half an hour
+ 1800 => 'H:i',
+ // Hour
+ 3600 => 'H:i',
+ // Four hours
+ 14400 => 'H:i',
+ // Six hours
+ 21600 => 'H:i',
+ // Half a day
+ 43200 => 'd.m a',
+ // Day
+ 86400 => 'd.m',
+ // Week
+ 604800 => 'W',
+ // Month
+ self::MONTH => 'M y',
+ // Year
+ self::YEAR => 'Y',
+ // Decade
+ self::DECADE => 'Y',
+ );
+
+ /**
+ * Constant used for calculation of automatic definition of major scaling
+ * steps
+ */
+ const MAJOR_COUNT = 10;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ $this->properties['startDate'] = false;
+ $this->properties['endDate'] = false;
+ $this->properties['interval'] = false;
+ $this->properties['dateFormat'] = false;
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * __set
+ *
+ * @param mixed $propertyName
+ * @param mixed $propertyValue
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'startDate':
+ $this->properties['startDate'] = (int) $propertyValue;
+ break;
+ case 'endDate':
+ $this->properties['endDate'] = (int) $propertyValue;
+ break;
+ case 'interval':
+ $this->properties['interval'] = (int) $propertyValue;
+ $this->properties['initialized'] = true;
+ break;
+ case 'dateFormat':
+ $this->properties['dateFormat'] = (string) $propertyValue;
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * Ensure proper timestamp
+ *
+ * Takes a mixed value from datasets, like timestamps, or strings
+ * describing some time and converts it to a timestamp.
+ *
+ * @param mixed $value
+ * @return int
+ */
+ protected static function ensureTimestamp( $value )
+ {
+ if ( is_numeric( $value ) )
+ {
+ $timestamp = (int) $value;
+ }
+ elseif ( ( $timestamp = strtotime( $value ) ) === false )
+ {
+ throw new ezcGraphErrorParsingDateException( $value );
+ }
+
+ return $timestamp;
+ }
+
+ /**
+ * Add data for this axis
+ *
+ * @param array $values Value which will be displayed on this axis
+ * @return void
+ */
+ public function addData( array $values )
+ {
+ foreach ( $values as $nr => $value )
+ {
+ $value = self::ensureTimestamp( $value );
+
+ if ( $this->minValue === false ||
+ $value < $this->minValue )
+ {
+ $this->minValue = $value;
+ }
+
+ if ( $this->maxValue === false ||
+ $value > $this->maxValue )
+ {
+ $this->maxValue = $value;
+ }
+ }
+
+ $this->properties['initialized'] = true;
+ }
+
+ /**
+ * Calculate nice time interval
+ *
+ * Use the best fitting time interval defined in class property array
+ * predefinedIntervals.
+ *
+ * @param int $min Start time
+ * @param int $max End time
+ * @return void
+ */
+ protected function calculateInterval( $min, $max )
+ {
+ $diff = $max - $min;
+
+ foreach ( $this->predefinedIntervals as $interval => $format )
+ {
+ if ( ( $diff / $interval ) <= self::MAJOR_COUNT )
+ {
+ break;
+ }
+ }
+
+ if ( ( $this->properties['startDate'] !== false ) &&
+ ( $this->properties['endDate'] !== false ) )
+ {
+ // Use interval between defined borders
+ if ( ( $diff % $interval ) > 0 )
+ {
+ // Stil use predefined date format from old interval if not set
+ if ( $this->properties['dateFormat'] === false )
+ {
+ $this->properties['dateFormat'] = $this->predefinedIntervals[$interval];
+ }
+
+ $count = ceil( $diff / $interval );
+ $interval = round( $diff / $count, 0 );
+ }
+ }
+
+ $this->properties['interval'] = $interval;
+ }
+
+ /**
+ * Calculate lower nice date
+ *
+ * Calculates a date which is earlier or equal to the given date, and is
+ * divisible by the given interval.
+ *
+ * @param int $min Date
+ * @param int $interval Interval
+ * @return int Earlier date
+ */
+ protected function calculateLowerNiceDate( $min, $interval )
+ {
+ switch ( $interval )
+ {
+ case self::MONTH:
+ // Special handling for months - not covered by the default
+ // algorithm
+ return mktime(
+ 1,
+ 0,
+ 0,
+ (int) date( 'm', $min ),
+ 1,
+ (int) date( 'Y', $min )
+ );
+ default:
+ $dateSteps = array( 60, 60, 24, 7, 52 );
+
+ $date = array(
+ (int) date( 's', $min ),
+ (int) date( 'i', $min ),
+ (int) date( 'H', $min ),
+ (int) date( 'd', $min ),
+ (int) date( 'm', $min ),
+ (int) date( 'Y', $min ),
+ );
+
+ $element = 0;
+ while ( ( $step = array_shift( $dateSteps ) ) &&
+ ( $interval > $step ) )
+ {
+ $interval /= $step;
+ $date[$element++] = (int) ( $element > 2 );
+ }
+
+ $date[$element] -= $date[$element] % $interval;
+
+ return mktime(
+ $date[2],
+ $date[1],
+ $date[0],
+ $date[4],
+ $date[3],
+ $date[5]
+ );
+ }
+ }
+
+ /**
+ * Calculate start date
+ *
+ * Use calculateLowerNiceDate to get a date earlier or equal date then the
+ * minimum date to use it as the start date for the axis depending on the
+ * selected interval.
+ *
+ * @param mixed $min Minimum date
+ * @param mixed $max Maximum date
+ * @return void
+ */
+ public function calculateMinimum( $min, $max )
+ {
+ if ( $this->properties['endDate'] === false )
+ {
+ $this->properties['startDate'] = $this->calculateLowerNiceDate( $min, $this->interval );
+ }
+ else
+ {
+ $this->properties['startDate'] = $this->properties['endDate'];
+
+ while ( $this->properties['startDate'] > $min )
+ {
+ switch ( $this->interval )
+ {
+ case self::MONTH:
+ $this->properties['startDate'] = strtotime( '-1 month', $this->properties['startDate'] );
+ break;
+ case self::YEAR:
+ $this->properties['startDate'] = strtotime( '-1 year', $this->properties['startDate'] );
+ break;
+ case self::DECADE:
+ $this->properties['startDate'] = strtotime( '-10 years', $this->properties['startDate'] );
+ break;
+ default:
+ $this->properties['startDate'] -= $this->interval;
+ }
+ }
+ }
+ }
+
+ /**
+ * Calculate end date
+ *
+ * Use calculateLowerNiceDate to get a date later or equal date then the
+ * maximum date to use it as the end date for the axis depending on the
+ * selected interval.
+ *
+ * @param mixed $min Minimum date
+ * @param mixed $max Maximum date
+ * @return void
+ */
+ public function calculateMaximum( $min, $max )
+ {
+ $this->properties['endDate'] = $this->properties['startDate'];
+
+ while ( $this->properties['endDate'] < $max )
+ {
+ switch ( $this->interval )
+ {
+ case self::MONTH:
+ $this->properties['endDate'] = strtotime( '+1 month', $this->properties['endDate'] );
+ break;
+ case self::YEAR:
+ $this->properties['endDate'] = strtotime( '+1 year', $this->properties['endDate'] );
+ break;
+ case self::DECADE:
+ $this->properties['endDate'] = strtotime( '+10 years', $this->properties['endDate'] );
+ break;
+ default:
+ $this->properties['endDate'] += $this->interval;
+ }
+ }
+ }
+
+ /**
+ * Calculate axis bounding values on base of the assigned values
+ *
+ * @return void
+ */
+ public function calculateAxisBoundings()
+ {
+ // Prevent division by zero, when min == max
+ if ( $this->minValue == $this->maxValue )
+ {
+ if ( $this->minValue == 0 )
+ {
+ $this->maxValue = 1;
+ }
+ else
+ {
+ $this->minValue -= ( $this->minValue * .1 );
+ $this->maxValue += ( $this->maxValue * .1 );
+ }
+ }
+
+ // Use custom minimum and maximum if available
+ if ( $this->properties['startDate'] !== false )
+ {
+ $this->minValue = $this->properties['startDate'];
+ }
+
+ if ( $this->properties['endDate'] !== false )
+ {
+ $this->maxValue = $this->properties['endDate'];
+ }
+
+ // Calculate "nice" values for scaling parameters
+ if ( $this->properties['interval'] === false )
+ {
+ $this->calculateInterval( $this->minValue, $this->maxValue );
+ }
+
+ if ( $this->properties['dateFormat'] === false && isset( $this->predefinedIntervals[$this->interval] ) )
+ {
+ $this->properties['dateFormat'] = $this->predefinedIntervals[$this->interval];
+ }
+
+ if ( $this->properties['startDate'] === false )
+ {
+ $this->calculateMinimum( $this->minValue, $this->maxValue );
+ }
+
+ if ( $this->properties['endDate'] === false )
+ {
+ $this->calculateMaximum( $this->minValue, $this->maxValue );
+ }
+ }
+
+ /**
+ * Get coordinate for a dedicated value on the chart
+ *
+ * @param float $value Value to determine position for
+ * @return float Position on chart
+ */
+ public function getCoordinate( $value )
+ {
+ // Force typecast, because ( false < -100 ) results in (bool) true
+ $intValue = ( $value === false ? false : self::ensureTimestamp( $value ) );
+
+ if ( ( $value === false ) &&
+ ( ( $intValue < $this->startDate ) || ( $intValue > $this->endDate ) ) )
+ {
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ return 0.;
+ case ezcGraph::RIGHT:
+ case ezcGraph::BOTTOM:
+ return 1.;
+ }
+ }
+ else
+ {
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ return ( $intValue - $this->startDate ) / ( $this->endDate - $this->startDate );
+ case ezcGraph::RIGHT:
+ case ezcGraph::BOTTOM:
+ return 1 - ( $intValue - $this->startDate ) / ( $this->endDate - $this->startDate );
+ }
+ }
+ }
+
+ /**
+ * Return count of minor steps
+ *
+ * @return integer Count of minor steps
+ */
+ public function getMinorStepCount()
+ {
+ return false;
+ }
+
+ /**
+ * Return count of major steps
+ *
+ * @return integer Count of major steps
+ */
+ public function getMajorStepCount()
+ {
+ return (int) ceil( ( $this->properties['endDate'] - $this->startDate ) / $this->interval );
+ }
+
+ /**
+ * Get label for a dedicated step on the axis
+ *
+ * @param integer $step Number of step
+ * @return string label
+ */
+ public function getLabel( $step )
+ {
+ return $this->getLabelFromTimestamp( $this->startDate + ( $step * $this->interval ), $step );
+ }
+
+ /**
+ * Get label for timestamp
+ *
+ * @param int $time
+ * @param int $step
+ * @return string
+ */
+ protected function getLabelFromTimestamp( $time, $step )
+ {
+ if ( $this->properties['labelCallback'] !== null )
+ {
+ return call_user_func_array(
+ $this->properties['labelCallback'],
+ array(
+ date( $this->properties['dateFormat'], $time ),
+ $step,
+ )
+ );
+ }
+ else
+ {
+ return date( $this->properties['dateFormat'], $time );
+ }
+ }
+
+ /**
+ * Return array of steps on this axis
+ *
+ * @return array( ezcGraphAxisStep )
+ */
+ public function getSteps()
+ {
+ $steps = array();
+
+ $start = $this->properties['startDate'];
+ $end = $this->properties['endDate'];
+ $distance = $end - $start;
+
+ $step = 0;
+ for ( $time = $start; $time <= $end; )
+ {
+ $steps[] = new ezcGraphAxisStep(
+ ( $time - $start ) / $distance,
+ $this->interval / $distance,
+ $this->getLabelFromTimestamp( $time, $step++ ),
+ array(),
+ $step === 1,
+ $time >= $end
+ );
+
+ switch ( $this->interval )
+ {
+ case self::MONTH:
+ $time = strtotime( '+1 month', $time );
+ break;
+ case self::YEAR:
+ $time = strtotime( '+1 year', $time );
+ break;
+ case self::DECADE:
+ $time = strtotime( '+10 years', $time );
+ break;
+ default:
+ $time += $this->interval;
+ break;
+ }
+ }
+
+ return $steps;
+ }
+
+ /**
+ * Is zero step
+ *
+ * Returns true if the given step is the one on the initial axis position
+ *
+ * @param int $step Number of step
+ * @return bool Status If given step is initial axis position
+ */
+ public function isZeroStep( $step )
+ {
+ return ( $step == 0 );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/axis/labeled.php b/include/ezcomponents/Graph/src/axis/labeled.php
new file mode 100644
index 000000000..434a0e894
--- /dev/null
+++ b/include/ezcomponents/Graph/src/axis/labeled.php
@@ -0,0 +1,448 @@
+properties['labelCount'] = null;
+
+ $this->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer();
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * __set
+ *
+ * @param mixed $propertyName
+ * @param mixed $propertyValue
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'labelCount':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue <= 1 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 1' );
+ }
+
+ $this->properties['labelCount'] = (int) $propertyValue;
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * Increase the keys of all elements in the array up from the start key, to
+ * insert an additional element at the correct position.
+ *
+ * @param array $array Array
+ * @param int $startKey Key to increase keys from
+ * @return array Updated array
+ */
+ protected function increaseKeys( array $array, $startKey )
+ {
+ foreach ( $array as $key => $value )
+ {
+ if ( $key === $startKey )
+ {
+ // Recursive check, if next key should be increased, too
+ if ( isset ( $array[$key + 1] ) )
+ {
+ $array = $this->increaseKeys( $array, $key + 1 );
+ }
+
+ // Increase key
+ $array[$key + 1] = $array[$key];
+ unset( $array[$key] );
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Add data for this axis
+ *
+ * @param array $values Value which will be displayed on this axis
+ * @return void
+ */
+ public function addData( array $values )
+ {
+ $position = 0;
+ foreach ( $values as $label )
+ {
+ $label = (string) $label;
+
+ if ( !in_array( $label, $this->labels, true ) )
+ {
+ if ( isset( $this->labels[$position] ) )
+ {
+ $this->labels = $this->increaseKeys( $this->labels, $position );
+ $this->labels[$position++] = $label;
+ }
+ else
+ {
+ $this->labels[$position++] = $label;
+ }
+ }
+ else
+ {
+ $position = array_search( $label, $this->labels, true ) + 1;
+ }
+ }
+ ksort( $this->labels );
+
+ $this->properties['initialized'] = true;
+ }
+
+ /**
+ * Calculate axis bounding values on base of the assigned values
+ *
+ * @abstract
+ * @access public
+ * @return void
+ */
+ public function calculateAxisBoundings()
+ {
+ $this->steps = array();
+
+ // Apply label format callback function
+ if ( $this->properties['labelCallback'] !== null )
+ {
+ foreach ( $this->labels as $nr => $label )
+ {
+ $this->labels[$nr] = call_user_func_array(
+ $this->properties['labelCallback'],
+ array(
+ $label,
+ $nr
+ )
+ );
+ }
+ }
+
+ $labelCount = count( $this->labels ) - 1;
+
+ if ( $labelCount === 0 )
+ {
+ // Create single only step
+ $this->steps = array(
+ new ezcGraphAxisStep(
+ 0,
+ 1,
+ reset( $this->labels ),
+ array(),
+ true,
+ true
+ ),
+ );
+
+ return true;
+ }
+
+ if ( $this->properties['labelCount'] === null )
+ {
+ if ( $labelCount <= self::MAX_LABEL_COUNT )
+ {
+ $stepSize = 1 / $labelCount;
+
+ foreach ( $this->labels as $nr => $label )
+ {
+ $this->steps[] = new ezcGraphAxisStep(
+ $stepSize * $nr,
+ $stepSize,
+ $label,
+ array(),
+ $nr === 0,
+ $nr === $labelCount
+ );
+ }
+
+ // @TODO: This line is deprecated and only build for
+ // deprecated getLabel()
+ $this->displayedLabels = $this->labels;
+
+ return true;
+ }
+
+ for ( $div = self::MAX_LABEL_COUNT; $div > 1; --$div )
+ {
+ if ( ( $labelCount % $div ) === 0 )
+ {
+ // @TODO: This part is deprecated and only build for
+ // deprecated getLabel()
+ $step = $labelCount / $div;
+
+ foreach ( $this->labels as $nr => $label )
+ {
+ if ( ( $nr % $step ) === 0 )
+ {
+ $this->displayedLabels[] = $label;
+ }
+ }
+ // End of deprecated part
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ $div = false;
+ }
+
+ // Build up step array
+ if ( $div > 2 )
+ {
+ $step = $labelCount / $div;
+ $stepSize = 1 / $div;
+ $minorStepSize = $stepSize / $step;
+
+ foreach ( $this->labels as $nr => $label )
+ {
+ if ( ( $nr % $step ) === 0 )
+ {
+ $mainstep = new ezcGraphAxisStep(
+ $stepSize * ( $nr / $step ),
+ $stepSize,
+ $label,
+ array(),
+ $nr === 0,
+ $nr === $labelCount
+ );
+
+ $this->steps[] = $mainstep;
+ }
+ else
+ {
+ $mainstep->childs[] = new ezcGraphAxisStep(
+ $mainstep->position + $minorStepSize * ( $nr % $step ),
+ $minorStepSize
+ );
+ }
+ }
+ }
+ else
+ {
+ if ( $this->properties['labelCount'] === null )
+ {
+ $floatStep = $labelCount / ( self::MAX_LABEL_COUNT - 1 );
+ }
+ else
+ {
+ $floatStep = $labelCount / min( $labelCount, $this->properties['labelCount'] - 1 );
+ }
+
+ $position = 0;
+ $minorStepSize = 1 / $labelCount;
+
+ foreach ( $this->labels as $nr => $label )
+ {
+ if ( $nr >= $position )
+ {
+ $position += $floatStep;
+
+ // Add as major step
+ $mainstep = new ezcGraphAxisStep(
+ $minorStepSize * $nr,
+ ceil( $position - $nr ) * $minorStepSize,
+ $label,
+ array(),
+ $nr === 0,
+ $nr === $labelCount
+ );
+
+ // @TODO: This line is deprecated and only build for
+ // deprecated getLabel()
+ $this->displayedLabels[] = $label;
+
+ $this->steps[] = $mainstep;
+ }
+ else
+ {
+ $mainstep->childs[] = new ezcGraphAxisStep(
+ $minorStepSize * $nr,
+ $minorStepSize
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Return array of steps on this axis
+ *
+ * @return array( ezcGraphAxisStep )
+ */
+ public function getSteps()
+ {
+ return $this->steps;
+ }
+
+ /**
+ * Get coordinate for a dedicated value on the chart
+ *
+ * @param string $value Value to determine position for
+ * @return float Position on chart
+ */
+ public function getCoordinate( $value )
+ {
+ if ( $value === false ||
+ $value === null ||
+ ( $key = array_search( $value, $this->labels ) ) === false )
+ {
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ return 0.;
+ case ezcGraph::RIGHT:
+ case ezcGraph::BOTTOM:
+ return 1.;
+ }
+ }
+ else
+ {
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ if ( count( $this->labels ) > 1 )
+ {
+ return (float) $key / ( count ( $this->labels ) - 1 );
+ }
+ else
+ {
+ return 0;
+ }
+ case ezcGraph::BOTTOM:
+ case ezcGraph::RIGHT:
+ if ( count( $this->labels ) > 1 )
+ {
+ return (float) 1 - $key / ( count ( $this->labels ) - 1 );
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return count of minor steps
+ *
+ * @return integer Count of minor steps
+ */
+ public function getMinorStepCount()
+ {
+ return 0;
+ }
+
+ /**
+ * Return count of major steps
+ *
+ * @return integer Count of major steps
+ */
+ public function getMajorStepCount()
+ {
+ return max( count( $this->displayedLabels ) - 1, 1 );
+ }
+
+ /**
+ * Get label for a dedicated step on the axis
+ *
+ * @param integer $step Number of step
+ * @return string label
+ */
+ public function getLabel( $step )
+ {
+ if ( isset( $this->displayedLabels[$step] ) )
+ {
+ return $this->displayedLabels[$step];
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Is zero step
+ *
+ * Returns true if the given step is the one on the initial axis position
+ *
+ * @param int $step Number of step
+ * @return bool Status If given step is initial axis position
+ */
+ public function isZeroStep( $step )
+ {
+ return !$step;
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/axis/logarithmic.php b/include/ezcomponents/Graph/src/axis/logarithmic.php
new file mode 100644
index 000000000..8c2a55add
--- /dev/null
+++ b/include/ezcomponents/Graph/src/axis/logarithmic.php
@@ -0,0 +1,303 @@
+properties['min'] = null;
+ $this->properties['max'] = null;
+ $this->properties['base'] = 10;
+ $this->properties['logarithmicalFormatString'] = '%1$d^%2$d';
+ $this->properties['minValue'] = null;
+ $this->properties['maxValue'] = null;
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * __set
+ *
+ * @param mixed $propertyName
+ * @param mixed $propertyValue
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'min':
+ case 'max':
+ if ( !is_numeric( $propertyValue ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' );
+ }
+
+ $this->properties[$propertyName] = (float) $propertyValue;
+ $this->properties['initialized'] = true;
+ break;
+ case 'base':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue <= 0 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' );
+ }
+
+ $this->properties[$propertyName] = (float) $propertyValue;
+ break;
+ case 'logarithmicalFormatString':
+ $this->properties['logarithmicalFormatString'] = (string) $propertyValue;
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * Add data for this axis
+ *
+ * @param array $values Value which will be displayed on this axis
+ * @return void
+ */
+ public function addData( array $values )
+ {
+ foreach ( $values as $value )
+ {
+ if ( $this->properties['minValue'] === null ||
+ $value < $this->properties['minValue'] )
+ {
+ $this->properties['minValue'] = $value;
+ }
+
+ if ( $this->properties['maxValue'] === null ||
+ $value > $this->properties['maxValue'] )
+ {
+ $this->properties['maxValue'] = $value;
+ }
+ }
+
+ $this->properties['initialized'] = true;
+ }
+
+ /**
+ * Calculate axis bounding values on base of the assigned values
+ *
+ * @abstract
+ * @access public
+ * @return void
+ */
+ public function calculateAxisBoundings()
+ {
+ // Prevent division by zero, when min == max
+ if ( $this->properties['minValue'] == $this->properties['maxValue'] )
+ {
+ if ( $this->properties['minValue'] == 0 )
+ {
+ $this->properties['maxValue'] = 1;
+ }
+ else
+ {
+ $this->properties['minValue'] -= ( $this->properties['minValue'] * .1 );
+ $this->properties['maxValue'] += ( $this->properties['maxValue'] * .1 );
+ }
+ }
+
+ if ( $this->properties['minValue'] <= 0 )
+ {
+ throw new ezcGraphOutOfLogithmicalBoundingsException( $this->properties['minValue'] );
+ }
+
+ // Use custom minimum and maximum if available
+ if ( $this->properties['min'] !== null )
+ {
+ $this->properties['minValue'] = pow( $this->properties['base'], $this->properties['min'] );
+ }
+
+ if ( $this->properties['max'] !== null )
+ {
+ $this->properties['maxValue'] = pow( $this->properties['base'], $this->properties['max'] );
+ }
+
+ // Calculate "nice" values for scaling parameters
+ if ( $this->properties['min'] === null )
+ {
+ $this->properties['min'] = floor( log( $this->properties['minValue'], $this->properties['base'] ) );
+ }
+
+ if ( $this->properties['max'] === null )
+ {
+ $this->properties['max'] = ceil( log( $this->properties['maxValue'], $this->properties['base'] ) );
+ }
+
+ $this->properties['minorStep'] = 1;
+ if ( ( $modifier = ( ( $this->properties['max'] - $this->properties['min'] ) / self::MAX_STEPS ) ) > 1 )
+ {
+ $this->properties['majorStep'] = $modifier = ceil( $modifier );
+ $this->properties['min'] = floor( $this->properties['min'] / $modifier ) * $modifier;
+ $this->properties['max'] = floor( $this->properties['max'] / $modifier ) * $modifier;
+ }
+ else
+ {
+ $this->properties['majorStep'] = 1;
+ }
+ }
+
+ /**
+ * Get coordinate for a dedicated value on the chart
+ *
+ * @param float $value Value to determine position for
+ * @return float Position on chart
+ */
+ public function getCoordinate( $value )
+ {
+ // Force typecast, because ( false < -100 ) results in (bool) true
+ $floatValue = (float) $value;
+
+ if ( $value === false )
+ {
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ return 0.;
+ case ezcGraph::RIGHT:
+ case ezcGraph::BOTTOM:
+ return 1.;
+ }
+ }
+ else
+ {
+ $position = ( log( $value, $this->properties['base'] ) - $this->properties['min'] ) / ( $this->properties['max'] - $this->properties['min'] );
+
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ return $position;
+ case ezcGraph::RIGHT:
+ case ezcGraph::BOTTOM:
+ return 1 - $position;
+ }
+ }
+ }
+
+ /**
+ * Return count of minor steps
+ *
+ * @return integer Count of minor steps
+ */
+ public function getMinorStepCount()
+ {
+ return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['minorStep'] );
+ }
+
+ /**
+ * Return count of major steps
+ *
+ * @return integer Count of major steps
+ */
+ public function getMajorStepCount()
+ {
+ return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['majorStep'] );
+ }
+
+ /**
+ * Get label for a dedicated step on the axis
+ *
+ * @param integer $step Number of step
+ * @return string label
+ */
+ public function getLabel( $step )
+ {
+ if ( $this->properties['labelCallback'] !== null )
+ {
+ return call_user_func_array(
+ $this->properties['labelCallback'],
+ array(
+ sprintf(
+ $this->properties['logarithmicalFormatString'],
+ $this->properties['base'],
+ $this->properties['min'] + ( $step * $this->properties['majorStep'] )
+ ),
+ $step,
+ )
+ );
+ }
+ else
+ {
+ return sprintf(
+ $this->properties['logarithmicalFormatString'],
+ $this->properties['base'],
+ $this->properties['min'] + ( $step * $this->properties['majorStep'] )
+ );
+ }
+ }
+
+ /**
+ * Is zero step
+ *
+ * Returns true if the given step is the one on the initial axis position
+ *
+ * @param int $step Number of step
+ * @return bool Status If given step is initial axis position
+ */
+ public function isZeroStep( $step )
+ {
+ return ( $step == 0 );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/axis/numeric.php b/include/ezcomponents/Graph/src/axis/numeric.php
new file mode 100644
index 000000000..9047e9901
--- /dev/null
+++ b/include/ezcomponents/Graph/src/axis/numeric.php
@@ -0,0 +1,422 @@
+properties['min'] = null;
+ $this->properties['max'] = null;
+ $this->properties['minValue'] = null;
+ $this->properties['maxValue'] = null;
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * __set
+ *
+ * @param mixed $propertyName
+ * @param mixed $propertyValue
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'min':
+ if ( !is_numeric( $propertyValue ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' );
+ }
+
+ $this->properties['min'] = (float) $propertyValue;
+ $this->properties['initialized'] = true;
+ break;
+ case 'max':
+ if ( !is_numeric( $propertyValue ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' );
+ }
+
+ $this->properties['max'] = (float) $propertyValue;
+ $this->properties['initialized'] = true;
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * Returns a "nice" number for a given floating point number.
+ *
+ * Nice numbers are steps on a scale which are easily recognized by humans
+ * like 0.5, 25, 1000 etc.
+ *
+ * @param float $float Number to be altered
+ * @return float Nice number
+ */
+ protected function getNiceNumber( $float )
+ {
+ // Get absolute value and save sign
+ $abs = abs( $float );
+ $sign = $float / $abs;
+
+ // Normalize number to a range between 1 and 10
+ $log = (int) round( log10( $abs ), 0 );
+ $abs /= pow( 10, $log );
+
+
+ // find next nice number
+ if ( $abs > 5 )
+ {
+ $abs = 10.;
+ }
+ elseif ( $abs > 2.5 )
+ {
+ $abs = 5.;
+ }
+ elseif ( $abs > 1 )
+ {
+ $abs = 2.5;
+ }
+ else
+ {
+ $abs = 1;
+ }
+
+ // unnormalize number to original values
+ return $abs * pow( 10, $log ) * $sign;
+ }
+
+ /**
+ * Calculate minimum value for displayed axe basing on real minimum and
+ * major step size
+ *
+ * @param float $min Real data minimum
+ * @param float $max Real data maximum
+ * @return void
+ */
+ protected function calculateMinimum( $min, $max )
+ {
+ if ( $this->properties['max'] === null )
+ {
+ $this->properties['min'] = floor( $min / $this->properties['majorStep'] ) * $this->properties['majorStep'];
+ }
+ else
+ {
+ $calculatedMin = $this->properties['max'];
+
+ do {
+ $calculatedMin -= $this->properties['majorStep'];
+ } while ( $calculatedMin > $min );
+
+ $this->properties['min'] = $calculatedMin;
+ }
+ }
+
+ /**
+ * Calculate maximum value for displayed axe basing on real maximum and
+ * major step size
+ *
+ * @param float $min Real data minimum
+ * @param float $max Real data maximum
+ * @return void
+ */
+ protected function calculateMaximum( $min, $max )
+ {
+ $calculatedMax = $this->properties['min'];
+
+ do {
+ $calculatedMax += $this->properties['majorStep'];
+ } while ( $calculatedMax < $max );
+
+ $this->properties['max'] = $calculatedMax;
+ }
+
+ /**
+ * Calculate size of minor steps based on the size of the major step size
+ *
+ * @param float $min Real data minimum
+ * @param float $max Real data maximum
+ * @return void
+ */
+ protected function calculateMinorStep( $min, $max )
+ {
+ $stepSize = $this->properties['majorStep'] / self::MIN_MINOR_COUNT;
+ $this->properties['minorStep'] = $this->getNiceNumber( $stepSize );
+ }
+
+ /**
+ * Calculate size of major step based on the span to be displayed and the
+ * defined MIN_MAJOR_COUNT constant.
+ *
+ * @param float $min Real data minimum
+ * @param float $max Real data maximum
+ * @return void
+ */
+ protected function calculateMajorStep( $min, $max )
+ {
+ $span = $max - $min;
+ $stepSize = $span / self::MIN_MAJOR_COUNT;
+ $this->properties['majorStep'] = $this->getNiceNumber( $stepSize );
+ }
+
+ /**
+ * Add data for this axis
+ *
+ * @param array $values Value which will be displayed on this axis
+ * @return void
+ */
+ public function addData( array $values )
+ {
+ foreach ( $values as $value )
+ {
+ if ( $this->properties['minValue'] === null ||
+ $value < $this->properties['minValue'] )
+ {
+ $this->properties['minValue'] = $value;
+ }
+
+ if ( $this->properties['maxValue'] === null ||
+ $value > $this->properties['maxValue'] )
+ {
+ $this->properties['maxValue'] = $value;
+ }
+ }
+
+ $this->properties['initialized'] = true;
+ }
+
+ /**
+ * Calculate axis bounding values on base of the assigned values
+ *
+ * @abstract
+ * @access public
+ * @return void
+ */
+ public function calculateAxisBoundings()
+ {
+ // Prevent division by zero, when min == max
+ if ( $this->properties['minValue'] == $this->properties['maxValue'] )
+ {
+ if ( $this->properties['minValue'] == 0 )
+ {
+ $this->properties['maxValue'] = 1;
+ }
+ else
+ {
+ if ( $this->properties['majorStep'] !== null )
+ {
+ $this->properties['minValue'] -= $this->properties['majorStep'];
+ $this->properties['maxValue'] += $this->properties['majorStep'];
+ }
+ else
+ {
+ $this->properties['minValue'] -= ( $this->properties['minValue'] * .1 );
+ $this->properties['maxValue'] += ( $this->properties['maxValue'] * .1 );
+ }
+ }
+ }
+
+ // Use custom minimum and maximum if available
+ if ( $this->properties['min'] !== null )
+ {
+ $this->properties['minValue'] = $this->properties['min'];
+ }
+
+ if ( $this->properties['max'] !== null )
+ {
+ $this->properties['maxValue'] = $this->properties['max'];
+ }
+
+ // If min and max values are forced, we may not be able to find a
+ // "nice" number for the steps. Try to find such a nice step size, or
+ // fall back to a step size, which is just the span divided by 5.
+ if ( ( $this->properties['min'] !== null ) &&
+ ( $this->properties['max'] !== null ) )
+ {
+ $diff = $this->properties['max'] - $this->properties['min'];
+ $this->calculateMajorStep( $this->properties['minValue'], $this->properties['maxValue'] );
+ $stepInvariance = $diff / $this->properties['majorStep'];
+ if ( ( $stepInvariance - floor( $stepInvariance ) ) > .0000001 )
+ {
+ // For too big step invariances calculate the step size just
+ // from the given difference between min and max value.
+ $this->properties['majorStep'] = ( $this->properties['max'] - $this->properties['min'] ) / self::MIN_MAJOR_COUNT;
+ $this->properties['minorStep'] = $this->properties['majorStep'] / self::MIN_MAJOR_COUNT;
+ }
+ }
+
+ // Calculate "nice" values for scaling parameters
+ if ( $this->properties['majorStep'] === null )
+ {
+ $this->calculateMajorStep( $this->properties['minValue'], $this->properties['maxValue'] );
+ }
+
+ if ( $this->properties['minorStep'] === null )
+ {
+ $this->calculateMinorStep( $this->properties['minValue'], $this->properties['maxValue'] );
+ }
+
+ if ( $this->properties['min'] === null )
+ {
+ $this->calculateMinimum( $this->properties['minValue'], $this->properties['maxValue'] );
+ }
+
+ if ( $this->properties['max'] === null )
+ {
+ $this->calculateMaximum( $this->properties['minValue'], $this->properties['maxValue'] );
+ }
+ }
+
+ /**
+ * Get coordinate for a dedicated value on the chart
+ *
+ * @param float $value Value to determine position for
+ * @return float Position on chart
+ */
+ public function getCoordinate( $value )
+ {
+ // Force typecast, because ( false < -100 ) results in (bool) true
+ $floatValue = (float) $value;
+
+ if ( ( $value === false ) &&
+ ( ( $floatValue < $this->properties['min'] ) || ( $floatValue > $this->properties['max'] ) ) )
+ {
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ return 0.;
+ case ezcGraph::RIGHT:
+ case ezcGraph::BOTTOM:
+ return 1.;
+ }
+ }
+ else
+ {
+ switch ( $this->position )
+ {
+ case ezcGraph::LEFT:
+ case ezcGraph::TOP:
+ return ( $value - $this->properties['min'] ) / ( $this->properties['max'] - $this->properties['min'] );
+ case ezcGraph::RIGHT:
+ case ezcGraph::BOTTOM:
+ return 1 - ( $value - $this->properties['min'] ) / ( $this->properties['max'] - $this->properties['min'] );
+ }
+ }
+ }
+
+ /**
+ * Return count of minor steps
+ *
+ * @return integer Count of minor steps
+ */
+ public function getMinorStepCount()
+ {
+ return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['minorStep'] );
+ }
+
+ /**
+ * Return count of major steps
+ *
+ * @return integer Count of major steps
+ */
+ public function getMajorStepCount()
+ {
+ return (int) ( ( $this->properties['max'] - $this->properties['min'] ) / $this->properties['majorStep'] );
+ }
+
+ /**
+ * Get label for a dedicated step on the axis
+ *
+ * @param integer $step Number of step
+ * @return string label
+ */
+ public function getLabel( $step )
+ {
+ if ( $this->properties['labelCallback'] !== null )
+ {
+ return call_user_func_array(
+ $this->properties['labelCallback'],
+ array(
+ $this->properties['min'] + ( $step * $this->properties['majorStep'] ),
+ $step,
+ )
+ );
+ }
+ else
+ {
+ return $this->properties['min'] + ( $step * $this->properties['majorStep'] );
+ }
+ }
+
+ /**
+ * Is zero step
+ *
+ * Returns true if the given step is the one on the initial axis position
+ *
+ * @param int $step Number of step
+ * @return bool Status If given step is initial axis position
+ */
+ public function isZeroStep( $step )
+ {
+ return ( $this->getLabel( $step ) == 0 );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/charts/bar.php b/include/ezcomponents/Graph/src/charts/bar.php
new file mode 100644
index 000000000..6352bd545
--- /dev/null
+++ b/include/ezcomponents/Graph/src/charts/bar.php
@@ -0,0 +1,94 @@
+
+ * // Create a new line chart
+ * $chart = new ezcGraphBarChart();
+ *
+ * // Add data to line chart
+ * $chart->data['sample dataset'] = new ezcGraphArrayDataSet(
+ * array(
+ * '100' => 1.2,
+ * '200' => 43.2,
+ * '300' => -34.14,
+ * '350' => 65,
+ * '400' => 123,
+ * )
+ * );
+ *
+ * // Render chart with default 2d renderer and default SVG driver
+ * $chart->render( 500, 200, 'bar_chart.svg' );
+ *
+ *
+ * Each chart consists of several chart elements which represents logical
+ * parts of the chart and can be formatted independently. The bar chart
+ * consists of:
+ * - title ( {@link ezcGraphChartElementText} )
+ * - legend ( {@link ezcGraphChartElementLegend} )
+ * - background ( {@link ezcGraphChartElementBackground} )
+ * - xAxis ( {@link ezcGraphChartElementLabeledAxis} )
+ * - yAxis ( {@link ezcGraphChartElementNumericAxis} )
+ *
+ * The type of the axis may be changed and all elements can be configured by
+ * accessing them as properties of the chart:
+ *
+ *
+ * $chart->legend->position = ezcGraph::RIGHT;
+ *
+ *
+ * The chart itself also offers several options to configure the appearance. As
+ * bar charts extend line charts the the extended configure options are
+ * available in {@link ezcGraphLineChartOptions} extending the
+ * {@link ezcGraphChartOptions}.
+ *
+ * @property ezcGraphLineChartOptions $options
+ * Chart options class
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphBarChart extends ezcGraphLineChart
+{
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ parent::__construct();
+
+ $this->elements['xAxis']->axisLabelRenderer = new ezcGraphAxisBoxedLabelRenderer();
+ }
+
+ /**
+ * Returns the default display type of the current chart type.
+ *
+ * @return int Display type
+ */
+ public function getDefaultDisplayType()
+ {
+ return ezcGraph::BAR;
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/charts/line.php b/include/ezcomponents/Graph/src/charts/line.php
new file mode 100644
index 000000000..bf1681aaf
--- /dev/null
+++ b/include/ezcomponents/Graph/src/charts/line.php
@@ -0,0 +1,652 @@
+
+ * // Create a new line chart
+ * $chart = new ezcGraphLineChart();
+ *
+ * // Add data to line chart
+ * $chart->data['sample dataset'] = new ezcGraphArrayDataSet(
+ * array(
+ * '100' => 1.2,
+ * '200' => 43.2,
+ * '300' => -34.14,
+ * '350' => 65,
+ * '400' => 123,
+ * )
+ * );
+ *
+ * // Render chart with default 2d renderer and default SVG driver
+ * $chart->render( 500, 200, 'line_chart.svg' );
+ *
+ *
+ * Each chart consists of several chart elements which represents logical
+ * parts of the chart and can be formatted independently. The line chart
+ * consists of:
+ * - title ( {@link ezcGraphChartElementText} )
+ * - legend ( {@link ezcGraphChartElementLegend} )
+ * - background ( {@link ezcGraphChartElementBackground} )
+ * - xAxis ( {@link ezcGraphChartElementLabeledAxis} )
+ * - yAxis ( {@link ezcGraphChartElementNumericAxis} )
+ *
+ * The type of the axis may be changed and all elements can be configured by
+ * accessing them as properties of the chart:
+ *
+ *
+ * $chart->legend->position = ezcGraph::RIGHT;
+ *
+ *
+ * The chart itself also offers several options to configure the appearance.
+ * The extended configure options are available in
+ * {@link ezcGraphLineChartOptions} extending the {@link ezcGraphChartOptions}.
+ *
+ * @property ezcGraphLineChartOptions $options
+ * Chart options class
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphLineChart extends ezcGraphChart
+{
+ /**
+ * Array with additional axis for the chart
+ *
+ * @var ezcGraphAxisContainer
+ */
+ protected $additionalAxis;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ $this->additionalAxis = new ezcGraphAxisContainer( $this );
+
+ $this->options = new ezcGraphLineChartOptions( $options );
+ $this->options->highlightFont = $this->options->font;
+
+ parent::__construct();
+
+ $this->addElement( 'xAxis', new ezcGraphChartElementLabeledAxis() );
+ $this->elements['xAxis']->position = ezcGraph::LEFT;
+
+ $this->addElement( 'yAxis', new ezcGraphChartElementNumericAxis() );
+ $this->elements['yAxis']->position = ezcGraph::BOTTOM;
+ }
+
+ /**
+ * __get
+ *
+ * @param mixed $propertyName
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return mixed
+ * @ignore
+ */
+ public function __get( $propertyName )
+ {
+ switch ( $propertyName )
+ {
+ case 'additionalAxis':
+ return $this->additionalAxis;
+ }
+
+ return parent::__get( $propertyName );
+ }
+
+ /**
+ * Options write access
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If Option could not be found
+ * @throws ezcBaseValueException
+ * If value is out of range
+ * @param mixed $propertyName Option name
+ * @param mixed $propertyValue Option value;
+ * @return mixed
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName ) {
+ case 'xAxis':
+ if ( $propertyValue instanceof ezcGraphChartElementAxis )
+ {
+ $this->addElement( 'xAxis', $propertyValue );
+ $this->elements['xAxis']->position = ezcGraph::LEFT;
+ }
+ else
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' );
+ }
+ break;
+ case 'yAxis':
+ if ( $propertyValue instanceof ezcGraphChartElementAxis )
+ {
+ $this->addElement( 'yAxis', $propertyValue );
+ $this->elements['yAxis']->position = ezcGraph::BOTTOM;
+ }
+ else
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' );
+ }
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ }
+ }
+
+ /**
+ * Set colors and border for this element
+ *
+ * @param ezcGraphPalette $palette Palette
+ * @return void
+ */
+ public function setFromPalette( ezcGraphPalette $palette )
+ {
+ foreach ( $this->additionalAxis as $element )
+ {
+ $element->setFromPalette( $palette );
+ }
+
+ parent::setFromPalette( $palette );
+ }
+
+ /**
+ * Render the assigned data
+ *
+ * Will renderer all charts data in the remaining boundings after drawing
+ * all other chart elements. The data will be rendered depending on the
+ * settings in the dataset.
+ *
+ * @param ezcGraphRenderer $renderer Renderer
+ * @param ezcGraphBoundings $boundings Remaining boundings
+ * @return void
+ */
+ protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings )
+ {
+ // Apply axis space
+ $xAxisSpace = ( $boundings->x1 - $boundings->x0 ) * $this->yAxis->axisSpace;
+ $yAxisSpace = ( $boundings->y1 - $boundings->y0 ) * $this->xAxis->axisSpace;
+
+ $boundings->x0 += $xAxisSpace;
+ $boundings->x1 -= $xAxisSpace;
+
+ $boundings->y0 += $yAxisSpace;
+ $boundings->y1 -= $yAxisSpace;
+
+ $yAxisNullPosition = $this->elements['yAxis']->getCoordinate( false );
+
+ // Initialize counters
+ $nr = array();
+ $count = array();
+
+ foreach ( $this->data as $data )
+ {
+ if ( !isset( $nr[$data->displayType->default] ) )
+ {
+ $nr[$data->displayType->default] = 0;
+ $count[$data->displayType->default] = 0;
+ }
+
+ $nr[$data->displayType->default]++;
+ $count[$data->displayType->default]++;
+ }
+
+ $checkedRegularSteps = false;
+
+ // Display data
+ foreach ( $this->data as $datasetName => $data )
+ {
+ --$nr[$data->displayType->default];
+
+ // Check which axis should be used
+ $xAxis = ( $data->xAxis->default ? $data->xAxis->default: $this->elements['xAxis'] );
+ $yAxis = ( $data->yAxis->default ? $data->yAxis->default: $this->elements['yAxis'] );
+
+ // Determine fill color for dataset
+ if ( $this->options->fillLines !== false )
+ {
+ $fillColor = clone $data->color->default;
+ $fillColor->alpha = (int) round( ( 255 - $fillColor->alpha ) * ( $this->options->fillLines / 255 ) );
+ }
+ else
+ {
+ $fillColor = null;
+ }
+
+ // Ensure regular steps on axis when used with bar charts and
+ // precalculate some values use to render bar charts
+ //
+ // Called only once and only when bars should be rendered
+ if ( ( $checkedRegularSteps === false ) &&
+ ( $data->displayType->default === ezcGraph::BAR ) )
+ {
+ $steps = $xAxis->getSteps();
+
+ $stepWidth = null;
+ foreach ( $steps as $step )
+ {
+ if ( $stepWidth === null )
+ {
+ $stepWidth = $step->width;
+ }
+ elseif ( $step->width !== $stepWidth )
+ {
+ throw new ezcGraphUnregularStepsException();
+ }
+ }
+
+ $step = reset( $steps );
+ if ( count( $step->childs ) )
+ {
+ // Keep this for BC reasons
+ $barCount = ( $xAxis->getMajorStepCount() + 1 ) * ( $xAxis->getMinorStepCount() - 1 );
+ $stepWidth = 1 / $barCount;
+ }
+
+ $checkedRegularSteps = true;
+ $width = $xAxis->axisLabelRenderer->modifyChartDataPosition(
+ $yAxis->axisLabelRenderer->modifyChartDataPosition(
+ new ezcGraphCoordinate(
+ ( $boundings->x1 - $boundings->x0 ) * $stepWidth,
+ 0
+ )
+ )
+ )->x;
+ }
+
+ // Draw lines for dataset
+ $lastPoint = false;
+ foreach ( $data as $key => $value )
+ {
+ // Calculate point in chart
+ $point = $xAxis->axisLabelRenderer->modifyChartDataPosition(
+ $yAxis->axisLabelRenderer->modifyChartDataPosition(
+ new ezcGraphCoordinate(
+ $xAxis->getCoordinate( $key ),
+ $yAxis->getCoordinate( $value )
+ )
+ )
+ );
+
+ // Render depending on display type of dataset
+ switch ( true )
+ {
+ case $data->displayType->default === ezcGraph::LINE:
+ $renderer->drawDataLine(
+ $boundings,
+ new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
+ $data->color->default,
+ ( $lastPoint === false ? $point : $lastPoint ),
+ $point,
+ $nr[$data->displayType->default],
+ $count[$data->displayType->default],
+ $data->symbol[$key],
+ $data->color[$key],
+ $fillColor,
+ $yAxisNullPosition,
+ ( $data->lineThickness->default ? $data->lineThickness->default : $this->options->lineThickness )
+ );
+ break;
+ case ( $data->displayType->default === ezcGraph::BAR ) &&
+ $this->options->stackBars :
+ // Check if a bar has already been stacked
+ if ( !isset( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] ) )
+ {
+ $start = new ezcGraphCoordinate(
+ $point->x,
+ $yAxisNullPosition
+ );
+
+ $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] = $value;
+ }
+ else
+ {
+ $start = $xAxis->axisLabelRenderer->modifyChartDataPosition(
+ $yAxis->axisLabelRenderer->modifyChartDataPosition(
+ new ezcGraphCoordinate(
+ $xAxis->getCoordinate( $key ),
+ $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] )
+ )
+ )
+ );
+
+ $point = $xAxis->axisLabelRenderer->modifyChartDataPosition(
+ $yAxis->axisLabelRenderer->modifyChartDataPosition(
+ new ezcGraphCoordinate(
+ $xAxis->getCoordinate( $key ),
+ $yAxis->getCoordinate( $stackedValue[(int) ( $point->x * 10000 )][(int) $value > 0] += $value )
+ )
+ )
+ );
+ }
+
+ // Force one symbol for each stacked bar
+ if ( !isset( $stackedSymbol[(int) ( $point->x * 10000 )] ) )
+ {
+ $stackedSymbol[(int) ( $point->x * 10000 )] = $data->symbol[$key];
+ }
+
+ // Store stacked value for next iteration
+ $stacked[(int) ( $point->x * 10000 )][$point->y / abs( $point->y )] = $point;
+
+ $renderer->drawStackedBar(
+ $boundings,
+ new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
+ $data->color->default,
+ $start,
+ $point,
+ $width,
+ $stackedSymbol[(int) ( $point->x * 10000 )],
+ $yAxisNullPosition
+ );
+ break;
+ case $data->displayType->default === ezcGraph::BAR:
+ $renderer->drawBar(
+ $boundings,
+ new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
+ $data->color[$key],
+ $point,
+ $width,
+ $nr[$data->displayType->default],
+ $count[$data->displayType->default],
+ $data->symbol[$key],
+ $yAxisNullPosition
+ );
+ break;
+ default:
+ throw new ezcGraphInvalidDisplayTypeException( $data->displayType->default );
+ break;
+ }
+
+ // Render highlight string if requested
+ if ( $data->highlight[$key] )
+ {
+ $renderer->drawDataHighlightText(
+ $boundings,
+ new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
+ $point,
+ $yAxisNullPosition,
+ $nr[$data->displayType->default],
+ $count[$data->displayType->default],
+ $this->options->highlightFont,
+ ( $data->highlightValue[$key] ? $data->highlightValue[$key] : $value ),
+ $this->options->highlightSize + $this->options->highlightFont->padding * 2,
+ ( $this->options->highlightLines ? $data->color[$key] : null )
+ );
+ }
+
+ // Store last point, used to connect lines in line chart.
+ $lastPoint = $point;
+ }
+ }
+ }
+
+ /**
+ * Returns the default display type of the current chart type.
+ *
+ * @return int Display type
+ */
+ public function getDefaultDisplayType()
+ {
+ return ezcGraph::LINE;
+ }
+
+ /**
+ * Check if renderer supports features requested by some special chart
+ * options.
+ *
+ * @throws ezcBaseValueException
+ * If some feature is not supported
+ *
+ * @return void
+ */
+ protected function checkRenderer()
+ {
+ // When stacked bars are enabled, check if renderer supports them
+ if ( $this->options->stackBars )
+ {
+ if ( !$this->renderer instanceof ezcGraphStackedBarsRenderer )
+ {
+ throw new ezcBaseValueException( 'renderer', $this->renderer, 'ezcGraphStackedBarsRenderer' );
+ }
+ }
+ }
+
+ /**
+ * Aggregate and calculate value boundings on axis.
+ *
+ * @return void
+ */
+ protected function setAxisValues()
+ {
+ // Virtual data set build for agrregated values sums for bar charts
+ $virtualBarSumDataSet = array( array(), array() );
+
+ // Calculate axis scaling and labeling
+ foreach ( $this->data as $dataset )
+ {
+ $nr = 0;
+ $labels = array();
+ $values = array();
+ foreach ( $dataset as $label => $value )
+ {
+ $labels[] = $label;
+ $values[] = $value;
+
+ // Build sum of all bars
+ if ( $this->options->stackBars &&
+ ( $dataset->displayType->default === ezcGraph::BAR ) )
+ {
+ if ( !isset( $virtualBarSumDataSet[(int) $value >= 0][$nr] ) )
+ {
+ $virtualBarSumDataSet[(int) $value >= 0][$nr++] = $value;
+ }
+ else
+ {
+ $virtualBarSumDataSet[(int) $value >= 0][$nr++] += $value;
+ }
+ }
+ }
+
+ // Check if data has been associated with another custom axis, use
+ // default axis otherwise.
+ if ( $dataset->xAxis->default )
+ {
+ $dataset->xAxis->default->addData( $labels );
+ }
+ else
+ {
+ $this->elements['xAxis']->addData( $labels );
+ }
+
+ if ( $dataset->yAxis->default )
+ {
+ $dataset->yAxis->default->addData( $values );
+ }
+ else
+ {
+ $this->elements['yAxis']->addData( $values );
+ }
+ }
+
+ // Also use stacked bar values as base for y axis value span
+ // calculation
+ if ( $this->options->stackBars )
+ {
+ $this->elements['yAxis']->addData( $virtualBarSumDataSet[0] );
+ $this->elements['yAxis']->addData( $virtualBarSumDataSet[1] );
+ }
+
+ // There should always be something assigned to the main x and y axis.
+ if ( !$this->elements['xAxis']->initialized ||
+ !$this->elements['yAxis']->initialized )
+ {
+ throw new ezcGraphNoDataException();
+ }
+
+ // Calculate boundings from assigned data
+ $this->elements['xAxis']->calculateAxisBoundings();
+ $this->elements['yAxis']->calculateAxisBoundings();
+ }
+
+ /**
+ * Renders the basic elements of this chart type
+ *
+ * @param int $width
+ * @param int $height
+ * @return void
+ */
+ protected function renderElements( $width, $height )
+ {
+ if ( !count( $this->data ) )
+ {
+ throw new ezcGraphNoDataException();
+ }
+
+ // Check if renderer supports requested features
+ $this->checkRenderer();
+
+ // Set values form datasets on axis to calculate correct spans
+ $this->setAxisValues();
+
+ // Generate legend
+ $this->elements['legend']->generateFromDataSets( $this->data );
+
+ // Get boundings from parameters
+ $this->options->width = $width;
+ $this->options->height = $height;
+
+ // Set image properties in driver
+ $this->driver->options->width = $width;
+ $this->driver->options->height = $height;
+
+ // Render subelements
+ $boundings = new ezcGraphBoundings();
+ $boundings->x1 = $this->options->width;
+ $boundings->y1 = $this->options->height;
+
+ $boundings = $this->elements['xAxis']->axisLabelRenderer->modifyChartBoundings(
+ $this->elements['yAxis']->axisLabelRenderer->modifyChartBoundings(
+ $boundings, new ezcGraphCoordinate( 1, 0 )
+ ), new ezcGraphCoordinate( -1, 0 )
+ );
+
+ // Render subelements
+ foreach ( $this->elements as $name => $element )
+ {
+ // Skip element, if it should not get rendered
+ if ( $this->renderElement[$name] === false )
+ {
+ continue;
+ }
+
+ // Special settings for special elements
+ switch ( $name )
+ {
+ case 'xAxis':
+ // get Position of 0 on the Y-axis for orientation of the x-axis
+ $element->nullPosition = $this->elements['yAxis']->getCoordinate( false );
+ break;
+ case 'yAxis':
+ // get Position of 0 on the X-axis for orientation of the y-axis
+ $element->nullPosition = $this->elements['xAxis']->getCoordinate( false );
+ break;
+ }
+
+ $this->driver->options->font = $element->font;
+ $boundings = $element->render( $this->renderer, $boundings );
+ }
+
+ // Render additional axis
+ foreach ( $this->additionalAxis as $element )
+ {
+ if ( $element->initialized )
+ {
+ // Calculate all required step sizes if values has been
+ // assigned to axis.
+ $element->calculateAxisBoundings();
+ }
+ else
+ {
+ // Do not render any axis labels, if no values were assigned
+ // and no step sizes were defined.
+ $element->axisLabelRenderer = new ezcGraphAxisNoLabelRenderer();
+ }
+
+ $this->driver->options->font = $element->font;
+ $element->nullPosition = $element->chartPosition;
+ $boundings = $element->render( $this->renderer, $boundings );
+ }
+
+ // Render graph
+ $this->renderData( $this->renderer, $boundings );
+ }
+
+ /**
+ * Render the line chart
+ *
+ * Renders the chart into a file or stream. The width and height are
+ * needed to specify the dimensions of the resulting image. For direct
+ * output use 'php://stdout' as output file.
+ *
+ * @param int $width Image width
+ * @param int $height Image height
+ * @param string $file Output file
+ * @apichange
+ * @return void
+ */
+ public function render( $width, $height, $file = null )
+ {
+ $this->renderElements( $width, $height );
+
+ if ( !empty( $file ) )
+ {
+ $this->renderer->render( $file );
+ }
+
+ $this->renderedFile = $file;
+ }
+
+ /**
+ * Renders this chart to direct output
+ *
+ * Does the same as ezcGraphChart::render(), but renders directly to
+ * output and not into a file.
+ *
+ * @param int $width
+ * @param int $height
+ * @apichange
+ * @return void
+ */
+ public function renderToOutput( $width, $height )
+ {
+ // @TODO: merge this function with render an deprecate ommit of third
+ // argument in render() when API break is possible
+ $this->renderElements( $width, $height );
+ $this->renderer->render( null );
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/charts/odometer.php b/include/ezcomponents/Graph/src/charts/odometer.php
new file mode 100644
index 000000000..6b221716b
--- /dev/null
+++ b/include/ezcomponents/Graph/src/charts/odometer.php
@@ -0,0 +1,296 @@
+
+ * $graph = new ezcGraphOdometerChart();
+ * $graph->title = 'Custom odometer';
+ *
+ * $graph->data['data'] = new ezcGraphArrayDataSet(
+ * array( 87 )
+ * );
+ *
+ * // Set the marker color
+ * $graph->data['data']->color[0] = '#A0000055';
+ *
+ * // Set colors for the background gradient
+ * $graph->options->startColor = '#2E3436';
+ * $graph->options->endColor = '#EEEEEC';
+ *
+ * // Define a border for the odometer
+ * $graph->options->borderWidth = 2;
+ * $graph->options->borderColor = '#BABDB6';
+ *
+ * // Set marker width
+ * $graph->options->markerWidth = 5;
+ *
+ * // Set space, which the odometer may consume
+ * $graph->options->odometerHeight = .7;
+ *
+ * // Set axis span and label
+ * $graph->axis->min = 0;
+ * $graph->axis->max = 100;
+ * $graph->axis->label = 'Coverage ';
+ *
+ * $graph->render( 400, 150, 'custom_odometer_chart.svg' );
+ *
+ *
+ * Each chart consists of several chart elements which represents logical parts
+ * of the chart and can be formatted independently. The odometer chart consists
+ * of:
+ * - title ( {@link ezcGraphChartElementText} )
+ * - background ( {@link ezcGraphChartElementBackground} )
+ *
+ * All elements can be configured by accessing them as properties of the chart:
+ *
+ *
+ * $chart->title->position = ezcGraph::BOTTOM;
+ *
+ *
+ * The chart itself also offers several options to configure the appearance.
+ * The extended configure options are available in
+ * {@link ezcGraphOdometerChartOptions} extending the {@link
+ * ezcGraphChartOptions}.
+ *
+ * @property ezcGraphOdometerChartOptions $options
+ * Chart options class
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphOdometerChart extends ezcGraphChart
+{
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ $this->options = new ezcGraphOdometerChartOptions( $options );
+
+ parent::__construct( $options );
+
+ $this->data = new ezcGraphChartSingleDataContainer( $this );
+
+ $this->addElement( 'axis', new ezcGraphChartElementNumericAxis());
+ $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer();
+ $this->elements['axis']->axisLabelRenderer->showZeroValue = true;
+ $this->elements['axis']->position = ezcGraph::LEFT;
+ $this->elements['axis']->axisSpace = .05;
+ }
+
+ /**
+ * Property write access
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If Option could not be found
+ * @throws ezcBaseValueException
+ * If value is out of range
+ * @param string $propertyName Option name
+ * @param mixed $propertyValue Option value;
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName ) {
+ case 'axis':
+ if ( $propertyValue instanceof ezcGraphChartElementAxis )
+ {
+ $this->addElement( 'axis', $propertyValue );
+ $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer();
+ $this->elements['axis']->axisLabelRenderer->showZeroValue = true;
+ $this->elements['axis']->position = ezcGraph::LEFT;
+ $this->elements['axis']->axisSpace = .05;
+ }
+ else
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' );
+ }
+ break;
+ case 'renderer':
+ if ( $propertyValue instanceof ezcGraphOdometerRenderer )
+ {
+ parent::__set( $propertyName, $propertyValue );
+ }
+ else
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphOdometerRenderer' );
+ }
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ }
+ }
+
+ /**
+ * Render the assigned data
+ *
+ * Will renderer all charts data in the remaining boundings after drawing
+ * all other chart elements. The data will be rendered depending on the
+ * settings in the dataset.
+ *
+ * @param ezcGraphRenderer $renderer Renderer
+ * @param ezcGraphBoundings $boundings Remaining boundings
+ * @return void
+ */
+ protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings )
+ {
+ // Draw the odometer data
+ $dataset = $this->data->rewind();
+
+ foreach ( $dataset as $key => $value )
+ {
+ $renderer->drawOdometerMarker(
+ $boundings,
+ $this->elements['axis']->axisLabelRenderer->modifyChartDataPosition(
+ new ezcGraphCoordinate(
+ $this->elements['axis']->getCoordinate( $value ),
+ 0
+ )
+ ),
+ $dataset->symbol[$key],
+ $dataset->color[$key],
+ $this->options->markerWidth
+ );
+ }
+ }
+
+ /**
+ * Returns the default display type of the current chart type.
+ *
+ * @return int Display type
+ */
+ public function getDefaultDisplayType()
+ {
+ return ezcGraph::ODOMETER;
+ }
+
+ /**
+ * Renders the basic elements of this chart type
+ *
+ * @param int $width
+ * @param int $height
+ * @return void
+ */
+ protected function renderElements( $width, $height )
+ {
+ if ( !count( $this->data ) )
+ {
+ throw new ezcGraphNoDataException();
+ }
+
+ // Set image properties in driver
+ $this->driver->options->width = $width;
+ $this->driver->options->height = $height;
+
+ // no legend
+ $this->renderElement['legend'] = false;
+
+ // Get boundings from parameters
+ $this->options->width = $width;
+ $this->options->height = $height;
+
+ $boundings = new ezcGraphBoundings();
+ $boundings->x1 = $this->options->width;
+ $boundings->y1 = $this->options->height;
+
+ // Get values out the single used dataset to calculate axis boundings
+ $values = array();
+ foreach ( $this->data->rewind() as $value )
+ {
+ $values[] = $value;
+ }
+
+ // Set values for Axis
+ $this->elements['axis']->addData( $values );
+ $this->elements['axis']->nullPosition = 0.5 + $this->options->odometerHeight / 2;
+ $this->elements['axis']->calculateAxisBoundings();
+
+ // Render subelements exept axis, which will be drawn together with the
+ // odometer bar
+ foreach ( $this->elements as $name => $element )
+ {
+ // Skip element, if it should not get rendered
+ if ( $this->renderElement[$name] === false ||
+ $name === 'axis' )
+ {
+ continue;
+ }
+
+ $this->driver->options->font = $element->font;
+ $boundings = $element->render( $this->renderer, $boundings );
+ }
+
+ // Draw basic odometer
+ $this->driver->options->font = $this->elements['axis']->font;
+ $boundings = $this->renderer->drawOdometer(
+ $boundings,
+ $this->elements['axis'],
+ $this->options
+ );
+
+ // Render graph
+ $this->renderData( $this->renderer, $boundings );
+ }
+
+ /**
+ * Render the pie chart
+ *
+ * Renders the chart into a file or stream. The width and height are
+ * needed to specify the dimensions of the resulting image. For direct
+ * output use 'php://stdout' as output file.
+ *
+ * @param int $width Image width
+ * @param int $height Image height
+ * @param string $file Output file
+ * @apichange
+ * @return void
+ */
+ public function render( $width, $height, $file = null )
+ {
+ $this->renderElements( $width, $height );
+
+ if ( !empty( $file ) )
+ {
+ $this->renderer->render( $file );
+ }
+
+ $this->renderedFile = $file;
+ }
+
+ /**
+ * Renders this chart to direct output
+ *
+ * Does the same as ezcGraphChart::render(), but renders directly to
+ * output and not into a file.
+ *
+ * @param int $width
+ * @param int $height
+ * @apichange
+ * @return void
+ */
+ public function renderToOutput( $width, $height )
+ {
+ // @TODO: merge this function with render an deprecate ommit of third
+ // argument in render() when API break is possible
+ $this->renderElements( $width, $height );
+ $this->renderer->render( null );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/charts/pie.php b/include/ezcomponents/Graph/src/charts/pie.php
new file mode 100644
index 000000000..885ade125
--- /dev/null
+++ b/include/ezcomponents/Graph/src/charts/pie.php
@@ -0,0 +1,308 @@
+
+ * // Create a new pie chart
+ * $chart = new ezcGraphPieChart();
+ *
+ * // Add data to line chart
+ * $chart->data['sample dataset'] = new ezcGraphArrayDataSet(
+ * array(
+ * 'one' => 1.2,
+ * 'two' => 43.2,
+ * 'three' => -34.14,
+ * 'four' => 65,
+ * 'five' => 123,
+ * )
+ * );
+ *
+ * // Render chart with default 2d renderer and default SVG driver
+ * $chart->render( 500, 200, 'pie_chart.svg' );
+ *
+ *
+ * Each chart consists of several chart elements which represents logical
+ * parts of the chart and can be formatted independently. The pie chart
+ * consists of:
+ * - title ( {@link ezcGraphChartElementText} )
+ * - legend ( {@link ezcGraphChartElementLegend} )
+ * - background ( {@link ezcGraphChartElementBackground} )
+ *
+ * All elements can be configured by accessing them as properties of the chart:
+ *
+ *
+ * $chart->legend->position = ezcGraph::RIGHT;
+ *
+ *
+ * The chart itself also offers several options to configure the appearance.
+ * The extended configure options are available in
+ * {@link ezcGraphPieChartOptions} extending the {@link ezcGraphChartOptions}.
+ *
+ * @property ezcGraphPieChartOptions $options
+ * Chart options class
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphPieChart extends ezcGraphChart
+{
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ $this->options = new ezcGraphPieChartOptions( $options );
+
+ parent::__construct( $options );
+
+ $this->data = new ezcGraphChartSingleDataContainer( $this );
+ }
+
+ /**
+ * Render the assigned data
+ *
+ * Will renderer all charts data in the remaining boundings after drawing
+ * all other chart elements. The data will be rendered depending on the
+ * settings in the dataset.
+ *
+ * @param ezcGraphRenderer $renderer Renderer
+ * @param ezcGraphBoundings $boundings Remaining boundings
+ * @return void
+ */
+ protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings )
+ {
+ // Only draw the first (and only) dataset
+ $dataset = $this->data->rewind();
+ $datasetName = $this->data->key();
+
+ $this->driver->options->font = $this->options->font;
+
+ // Calculate sum of all values to be able to calculate percentage
+ $sum = 0;
+ foreach ( $dataset as $name => $value )
+ {
+ if ( $value < 0 )
+ {
+ throw new ezcGraphInvalidDataException( "Values >= 0 required, '$name' => '$value'." );
+ }
+
+ $sum += $value;
+ }
+ if ( $this->options->sum !== false )
+ {
+ $sum = max( $sum, $this->options->sum );
+ }
+
+ if ( $sum <= 0 )
+ {
+ throw new ezcGraphInvalidDataException( "Pie charts require a value sum > 0, your value: '$sum'." );
+ }
+
+ $angle = 0;
+ foreach ( $dataset as $label => $value )
+ {
+ // Skip rendering values which equals 0
+ if ( $value <= 0 )
+ {
+ continue;
+ }
+
+ switch ( $dataset->displayType->default )
+ {
+ case ezcGraph::PIE:
+ $displayLabel = ( $this->options->labelCallback !== null
+ ? call_user_func( $this->options->labelCallback, $label, $value, $value / $sum )
+ : sprintf( $this->options->label, $label, $value, $value / $sum * 100 ) );
+
+ $renderer->drawPieSegment(
+ $boundings,
+ new ezcGraphContext( $datasetName, $label, $dataset->url[$label] ),
+ $dataset->color[$label],
+ $angle,
+ $angle += $value / $sum * 360,
+ $displayLabel,
+ $dataset->highlight[$label]
+ );
+ break;
+ default:
+ throw new ezcGraphInvalidDisplayTypeException( $dataset->displayType->default );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Returns the default display type of the current chart type.
+ *
+ * @return int Display type
+ */
+ public function getDefaultDisplayType()
+ {
+ return ezcGraph::PIE;
+ }
+
+ /**
+ * Apply tresh hold
+ *
+ * Iterates over the dataset and applies the configured tresh hold to
+ * the datasets data.
+ *
+ * @return void
+ */
+ protected function applyThreshold()
+ {
+ if ( $this->options->percentThreshold || $this->options->absoluteThreshold )
+ {
+ $dataset = $this->data->rewind();
+
+ $sum = 0;
+ foreach ( $dataset as $value )
+ {
+ $sum += $value;
+ }
+ if ( $this->options->sum !== false )
+ {
+ $sum = max( $sum, $this->options->sum );
+ }
+
+ $unset = array();
+ foreach ( $dataset as $label => $value )
+ {
+ if ( $label === $this->options->summarizeCaption )
+ {
+ continue;
+ }
+
+ if ( ( $value <= $this->options->absoluteThreshold ) ||
+ ( ( $value / $sum ) <= $this->options->percentThreshold ) )
+ {
+ if ( !isset( $dataset[$this->options->summarizeCaption] ) )
+ {
+ $dataset[$this->options->summarizeCaption] = $value;
+ }
+ else
+ {
+ $dataset[$this->options->summarizeCaption] += $value;
+ }
+
+ $unset[] = $label;
+ }
+ }
+
+ foreach ( $unset as $label )
+ {
+ unset( $dataset[$label] );
+ }
+ }
+ }
+
+ /**
+ * Renders the basic elements of this chart type
+ *
+ * @param int $width
+ * @param int $height
+ * @return void
+ */
+ protected function renderElements( $width, $height )
+ {
+ if ( !count( $this->data ) )
+ {
+ throw new ezcGraphNoDataException();
+ }
+
+ // Set image properties in driver
+ $this->driver->options->width = $width;
+ $this->driver->options->height = $height;
+
+ // Apply tresh hold
+ $this->applyThreshold();
+
+ // Generate legend
+ $this->elements['legend']->generateFromDataSet( $this->data->rewind() );
+
+ // Get boundings from parameters
+ $this->options->width = $width;
+ $this->options->height = $height;
+
+ $boundings = new ezcGraphBoundings();
+ $boundings->x1 = $this->options->width;
+ $boundings->y1 = $this->options->height;
+
+ // Render subelements
+ foreach ( $this->elements as $name => $element )
+ {
+ // Skip element, if it should not get rendered
+ if ( $this->renderElement[$name] === false )
+ {
+ continue;
+ }
+
+ $this->driver->options->font = $element->font;
+ $boundings = $element->render( $this->renderer, $boundings );
+ }
+
+ // Render graph
+ $this->renderData( $this->renderer, $boundings );
+ }
+
+ /**
+ * Render the pie chart
+ *
+ * Renders the chart into a file or stream. The width and height are
+ * needed to specify the dimensions of the resulting image. For direct
+ * output use 'php://stdout' as output file.
+ *
+ * @param int $width Image width
+ * @param int $height Image height
+ * @param string $file Output file
+ * @apichange
+ * @return void
+ */
+ public function render( $width, $height, $file = null )
+ {
+ $this->renderElements( $width, $height );
+
+ if ( !empty( $file ) )
+ {
+ $this->renderer->render( $file );
+ }
+
+ $this->renderedFile = $file;
+ }
+
+ /**
+ * Renders this chart to direct output
+ *
+ * Does the same as ezcGraphChart::render(), but renders directly to
+ * output and not into a file.
+ *
+ * @param int $width
+ * @param int $height
+ * @apichange
+ * @return void
+ */
+ public function renderToOutput( $width, $height )
+ {
+ // @TODO: merge this function with render an deprecate ommit of third
+ // argument in render() when API break is possible
+ $this->renderElements( $width, $height );
+ $this->renderer->render( null );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/charts/radar.php b/include/ezcomponents/Graph/src/charts/radar.php
new file mode 100644
index 000000000..c63ed6868
--- /dev/null
+++ b/include/ezcomponents/Graph/src/charts/radar.php
@@ -0,0 +1,457 @@
+
+ * // Create a new radar chart
+ * $chart = new ezcGraphRadarChart();
+ *
+ * // Add data to line chart
+ * $chart->data['sample dataset'] = new ezcGraphArrayDataSet(
+ * array(
+ * '100' => 1.2,
+ * '200' => 43.2,
+ * '300' => -34.14,
+ * '350' => 65,
+ * '400' => 123,
+ * )
+ * );
+ *
+ * // Render chart with default 2d renderer and default SVG driver
+ * $chart->render( 500, 200, 'radar_chart.svg' );
+ *
+ *
+ * Each chart consists of several chart elements which represents logical
+ * parts of the chart and can be formatted independently. The line chart
+ * consists of:
+ * - title ( {@link ezcGraphChartElementText} )
+ * - legend ( {@link ezcGraphChartElementLegend} )
+ * - background ( {@link ezcGraphChartElementBackground} )
+ * - axis ( {@link ezcGraphChartElementNumericAxis} )
+ * - ratation axis ( {@link ezcGraphChartElementLabeledAxis} )
+ *
+ * The type of the axis may be changed and all elements can be configured by
+ * accessing them as properties of the chart:
+ *
+ * The chart itself also offers several options to configure the appearance.
+ * The extended configure options are available in
+ * {@link ezcGraphRadarChartOptions} extending the
+ * {@link ezcGraphChartOptions}.
+ *
+ *
+ * $chart->legend->position = ezcGraph::RIGHT;
+ *
+ *
+ * @property ezcGraphRadarChartOptions $options
+ * Chart options class
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphRadarChart extends ezcGraphChart
+{
+ /**
+ * Store major grid color for child axis.
+ *
+ * @var ezcGraphColor
+ */
+ protected $childAxisColor;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ $this->options = new ezcGraphRadarChartOptions( $options );
+ $this->options->highlightFont = $this->options->font;
+
+ parent::__construct();
+
+ $this->elements['rotationAxis'] = new ezcGraphChartElementLabeledAxis();
+
+ $this->addElement( 'axis', new ezcGraphChartElementNumericAxis() );
+ $this->elements['axis']->position = ezcGraph::BOTTOM;
+ $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisRadarLabelRenderer();
+ $this->elements['axis']->axisLabelRenderer->outerStep = true;
+
+ $this->addElement( 'rotationAxis', new ezcGraphChartElementLabeledAxis() );
+
+ // Do not render axis with default method, because we need an axis for
+ // each label in dataset
+ $this->renderElement['axis'] = false;
+ $this->renderElement['rotationAxis'] = false;
+ }
+
+ /**
+ * Set colors and border fro this element
+ *
+ * @param ezcGraphPalette $palette Palette
+ * @return void
+ */
+ public function setFromPalette( ezcGraphPalette $palette )
+ {
+ $this->childAxisColor = $palette->majorGridColor;
+
+ parent::setFromPalette( $palette );
+ }
+
+ /**
+ * Property write access
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If Option could not be found
+ * @throws ezcBaseValueException
+ * If value is out of range
+ * @param string $propertyName Option name
+ * @param mixed $propertyValue Option value;
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName ) {
+ case 'axis':
+ if ( $propertyValue instanceof ezcGraphChartElementAxis )
+ {
+ $this->addElement( 'axis', $propertyValue );
+ $this->elements['axis']->position = ezcGraph::BOTTOM;
+ $this->elements['axis']->axisLabelRenderer = new ezcGraphAxisRadarLabelRenderer();
+ $this->renderElement['axis'] = false;
+ }
+ else
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' );
+ }
+ break;
+ case 'rotationAxis':
+ if ( $propertyValue instanceof ezcGraphChartElementAxis )
+ {
+ $this->addElement( 'rotationAxis', $propertyValue );
+ $this->renderElement['rotationAxis'] = false;
+ }
+ else
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphChartElementAxis' );
+ }
+ break;
+ case 'renderer':
+ if ( $propertyValue instanceof ezcGraphRadarRenderer )
+ {
+ parent::__set( $propertyName, $propertyValue );
+ }
+ else
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphRadarRenderer' );
+ }
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ }
+ }
+
+ /**
+ * Draws a single rotated axis
+ *
+ * Sets the axis label position depending on the axis rotation.
+ *
+ * @param ezcGraphChartElementAxis $axis
+ * @param ezcGraphBoundings $boundings
+ * @param ezcGraphCoordinate $center
+ * @param float $position
+ * @param float $lastPosition
+ * @return void
+ */
+ protected function drawRotatedAxis( ezcGraphChartElementAxis $axis, ezcGraphBoundings $boundings, ezcGraphCoordinate $center, $position, $lastPosition = null )
+ {
+ // Set axis position depending on angle for better axis label
+ // positioning
+ $angle = $position * 2 * M_PI;
+ switch ( (int) ( ( $position + .125 ) * 4 ) )
+ {
+ case 0:
+ case 4:
+ $axis->position = ezcGraph::BOTTOM;
+ break;
+ case 1:
+ $axis->position = ezcGraph::LEFT;
+ break;
+ case 2:
+ $axis->position = ezcGraph::TOP;
+ break;
+ case 3:
+ $axis->position = ezcGraph::RIGHT;
+ break;
+ }
+
+ // Set last step to correctly draw grid
+ if ( $axis->axisLabelRenderer instanceof ezcGraphAxisRadarLabelRenderer )
+ {
+ $axis->axisLabelRenderer->lastStep = $lastPosition;
+ }
+
+ // Do not draw axis label for last step
+ if ( abs( $position - 1 ) <= .001 )
+ {
+ $axis->label = null;
+ }
+
+ $this->renderer->drawAxis(
+ $boundings,
+ clone $center,
+ $dest = new ezcGraphCoordinate(
+ $center->x + sin( $angle ) * ( $boundings->width / 2 ),
+ $center->y - cos( $angle ) * ( $boundings->height / 2 )
+ ),
+ clone $axis,
+ clone $axis->axisLabelRenderer
+ );
+ }
+
+ /**
+ * Render the assigned data
+ *
+ * Will renderer all charts data in the remaining boundings after drawing
+ * all other chart elements. The data will be rendered depending on the
+ * settings in the dataset.
+ *
+ * @param ezcGraphRenderer $renderer Renderer
+ * @param ezcGraphBoundings $boundings Remaining boundings
+ * @return void
+ */
+ protected function renderData( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings )
+ {
+ // Apply axis space
+ $xAxisSpace = ( $boundings->x1 - $boundings->x0 ) * $this->axis->axisSpace;
+ $yAxisSpace = ( $boundings->y1 - $boundings->y0 ) * $this->axis->axisSpace;
+
+ $center = new ezcGraphCoordinate(
+ ( $boundings->width / 2 ),
+ ( $boundings->height / 2 )
+ );
+
+ // We do not differentiate between display types in radar charts.
+ $nr = $count = count( $this->data );
+
+ // Draw axis at major steps of virtual axis
+ $steps = $this->elements['rotationAxis']->getSteps();
+ $lastStepPosition = null;
+ $axisColor = $this->elements['axis']->border;
+ foreach ( $steps as $step )
+ {
+ $this->elements['axis']->label = $step->label;
+ $this->drawRotatedAxis( $this->elements['axis'], $boundings, $center, $step->position, $lastStepPosition );
+ $lastStepPosition = $step->position;
+
+ if ( count( $step->childs ) )
+ {
+ foreach ( $step->childs as $childStep )
+ {
+ $this->elements['axis']->label = null;
+ $this->elements['axis']->border = $this->childAxisColor;
+
+ $this->drawRotatedAxis( $this->elements['axis'], $boundings, $center, $childStep->position, $lastStepPosition );
+ $lastStepPosition = $childStep->position;
+ }
+ }
+
+ $this->elements['axis']->border = $axisColor;
+ }
+
+ // Display data
+ $this->elements['axis']->position = ezcGraph::TOP;
+ foreach ( $this->data as $datasetName => $data )
+ {
+ --$nr;
+ // Determine fill color for dataset
+ if ( $this->options->fillLines !== false )
+ {
+ $fillColor = clone $data->color->default;
+ $fillColor->alpha = (int) round( ( 255 - $fillColor->alpha ) * ( $this->options->fillLines / 255 ) );
+ }
+ else
+ {
+ $fillColor = null;
+ }
+
+ // Draw lines for dataset
+ $lastPoint = false;
+ foreach ( $data as $key => $value )
+ {
+ $point = new ezcGraphCoordinate(
+ $this->elements['rotationAxis']->getCoordinate( $key ),
+ $this->elements['axis']->getCoordinate( $value )
+ );
+
+ /* Transformation required for 3d like renderers ...
+ * which axis should transform here?
+ $point = $this->elements['xAxis']->axisLabelRenderer->modifyChartDataPosition(
+ $this->elements['yAxis']->axisLabelRenderer->modifyChartDataPosition(
+ new ezcGraphCoordinate(
+ $this->elements['xAxis']->getCoordinate( $key ),
+ $this->elements['yAxis']->getCoordinate( $value )
+ )
+ )
+ );
+ // */
+
+ $renderer->drawRadarDataLine(
+ $boundings,
+ new ezcGraphContext( $datasetName, $key, $data->url[$key] ),
+ $data->color->default,
+ clone $center,
+ ( $lastPoint === false ? $point : $lastPoint ),
+ $point,
+ $nr,
+ $count,
+ $data->symbol[$key],
+ $data->color[$key],
+ $fillColor,
+ $this->options->lineThickness
+ );
+
+ $lastPoint = $point;
+ }
+ }
+ }
+
+ /**
+ * Returns the default display type of the current chart type.
+ *
+ * @return int Display type
+ */
+ public function getDefaultDisplayType()
+ {
+ return ezcGraph::LINE;
+ }
+
+ /**
+ * Renders the basic elements of this chart type
+ *
+ * @param int $width
+ * @param int $height
+ * @return void
+ */
+ protected function renderElements( $width, $height )
+ {
+ if ( !count( $this->data ) )
+ {
+ throw new ezcGraphNoDataException();
+ }
+
+ // Set image properties in driver
+ $this->driver->options->width = $width;
+ $this->driver->options->height = $height;
+
+ // Calculate axis scaling and labeling
+ foreach ( $this->data as $dataset )
+ {
+ $labels = array();
+ $values = array();
+ foreach ( $dataset as $label => $value )
+ {
+ $labels[] = $label;
+ $values[] = $value;
+ }
+
+ $this->elements['axis']->addData( $values );
+ $this->elements['rotationAxis']->addData( $labels );
+ }
+
+ $this->elements['axis']->calculateAxisBoundings();
+ $this->elements['rotationAxis']->calculateAxisBoundings();
+
+ // Generate legend
+ $this->elements['legend']->generateFromDataSets( $this->data );
+
+ // Get boundings from parameters
+ $this->options->width = $width;
+ $this->options->height = $height;
+
+ // Render subelements
+ $boundings = new ezcGraphBoundings();
+ $boundings->x1 = $this->options->width;
+ $boundings->y1 = $this->options->height;
+
+ // Render subelements
+ foreach ( $this->elements as $name => $element )
+ {
+ // Skip element, if it should not get rendered
+ if ( $this->renderElement[$name] === false )
+ {
+ continue;
+ }
+
+ $this->driver->options->font = $element->font;
+ $boundings = $element->render( $this->renderer, $boundings );
+ }
+
+ // Render graph
+ $this->renderData( $this->renderer, $boundings );
+ }
+
+ /**
+ * Render the line chart
+ *
+ * Renders the chart into a file or stream. The width and height are
+ * needed to specify the dimensions of the resulting image. For direct
+ * output use 'php://stdout' as output file.
+ *
+ * @param int $width Image width
+ * @param int $height Image height
+ * @param string $file Output file
+ * @apichange
+ * @return void
+ */
+ public function render( $width, $height, $file = null )
+ {
+ $this->renderElements( $width, $height );
+
+ if ( !empty( $file ) )
+ {
+ $this->renderer->render( $file );
+ }
+
+ $this->renderedFile = $file;
+ }
+
+ /**
+ * Renders this chart to direct output
+ *
+ * Does the same as ezcGraphChart::render(), but renders directly to
+ * output and not into a file.
+ *
+ * @param int $width
+ * @param int $height
+ * @apichange
+ * @return void
+ */
+ public function renderToOutput( $width, $height )
+ {
+ // @TODO: merge this function with render an deprecate ommit of third
+ // argument in render() when API break is possible
+ $this->renderElements( $width, $height );
+ $this->renderer->render( null );
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/colors/color.php b/include/ezcomponents/Graph/src/colors/color.php
new file mode 100644
index 000000000..8ad4c9a7e
--- /dev/null
+++ b/include/ezcomponents/Graph/src/colors/color.php
@@ -0,0 +1,269 @@
+properties['red'] = 0;
+ $this->properties['green'] = 0;
+ $this->properties['blue'] = 0;
+ $this->properties['alpha'] = 0;
+
+ parent::__construct( $options );
+ }
+
+ /**
+ * __set
+ *
+ * @param mixed $propertyName
+ * @param mixed $propertyValue
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'red':
+ case 'green':
+ case 'blue':
+ case 'alpha':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue < 0 ) ||
+ ( $propertyValue > 255 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 255' );
+ }
+
+ $this->properties[$propertyName] = (int) $propertyValue;
+ break;
+ default:
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ break;
+ }
+ }
+
+ /**
+ * Creates an ezcGraphColor object from a hexadecimal color representation
+ *
+ * @param mixed $string Hexadecimal color representation
+ * @return ezcGraphColor
+ */
+ static public function fromHex( $string )
+ {
+ // Remove trailing #
+ if ( $string[0] === '#' )
+ {
+ $string = substr( $string, 1 );
+ }
+
+ // Iterate over chunks and convert to integer
+ $color = new ezcGraphColor();
+ $keys = array( 'red', 'green', 'blue', 'alpha' );
+ foreach ( str_split( $string, 2) as $nr => $hexValue )
+ {
+ if ( isset( $keys[$nr] ) )
+ {
+ $key = $keys[$nr];
+ $color->$key = hexdec( $hexValue ) % 256;
+ }
+ }
+
+ // Set missing values to zero
+ for ( ++$nr; $nr < count( $keys ); ++$nr )
+ {
+ $key = $keys[$nr];
+ $color->$key = 0;
+ }
+
+ return $color;
+ }
+
+ /**
+ * Creates an ezcGraphColor object from an array of integers
+ *
+ * @param array $array Array of integer color values
+ * @return ezcGraphColor
+ */
+ static public function fromIntegerArray( array $array )
+ {
+ // Iterate over array elements
+ $color = new ezcGraphColor();
+ $keys = array( 'red', 'green', 'blue', 'alpha' );
+ $nr = 0;
+ foreach ( $array as $colorValue )
+ {
+ if ( isset( $keys[$nr] ) )
+ {
+ $key = $keys[$nr++];
+ $color->$key = ( (int) $colorValue ) % 256;
+ }
+ }
+
+ // Set missing values to zero
+ for ( $nr; $nr < count( $keys ); ++$nr )
+ {
+ $key = $keys[$nr];
+ $color->$key = 0;
+ }
+
+ return $color;
+ }
+
+ /**
+ * Creates an ezcGraphColor object from an array of floats
+ *
+ * @param array $array Array of float color values
+ * @return ezcGraphColor
+ */
+ static public function fromFloatArray( array $array )
+ {
+ // Iterate over array elements
+ $color = new ezcGraphColor();
+ $keys = array( 'red', 'green', 'blue', 'alpha' );
+ $nr = 0;
+ foreach ( $array as $colorValue )
+ {
+ if ( isset( $keys[$nr] ) )
+ {
+ $key = $keys[$nr++];
+ $color->$key = ( (float) $colorValue * 255 ) % 256;
+ }
+ }
+
+ // Set missing values to zero
+ for ( $nr; $nr < count( $keys ); ++$nr )
+ {
+ $key = $keys[$nr];
+ $color->$key = 0;
+ }
+
+ return $color;
+ }
+
+ /**
+ * Tries to detect type of color color definition and returns an
+ * ezcGraphColor object
+ *
+ * @param mixed $color Some kind of color definition
+ * @return ezcGraphColor
+ */
+ static public function create( $color )
+ {
+ if ( $color instanceof ezcGraphColor )
+ {
+ return $color;
+ }
+ elseif ( is_string( $color ) )
+ {
+ return ezcGraphColor::fromHex( $color );
+ }
+ elseif ( is_array( $color ) )
+ {
+ $testElement = reset( $color );
+ if ( is_int( $testElement ) )
+ {
+ return ezcGraphColor::fromIntegerArray( $color );
+ }
+ else
+ {
+ return ezcGraphColor::fromFloatArray( $color );
+ }
+ }
+ else
+ {
+ throw new ezcGraphUnknownColorDefinitionException( $color );
+ }
+ }
+
+ /**
+ * Returns a copy of the current color made more transparent by the given
+ * factor
+ *
+ * @param mixed $value Percent to make color mor transparent
+ * @return ezcGraphColor New color
+ */
+ public function transparent( $value )
+ {
+ $color = clone $this;
+
+ $color->alpha = 255 - (int) round( ( 255 - $this->alpha ) * ( 1 - $value ) );
+
+ return $color;
+ }
+
+ /**
+ * Inverts and returns a copy of the current color
+ *
+ * @return ezcGraphColor New Color
+ */
+ public function invert()
+ {
+ $color = new ezcGraphColor();
+
+ $color->red = 255 - $this->red;
+ $color->green = 255 - $this->green;
+ $color->blue = 255 - $this->blue;
+ $color->alpha = $this->alpha;
+
+ return $color;
+ }
+
+ /**
+ * Returns a copy of the current color darkened by the given factor
+ *
+ * @param float $value Percent to darken the color
+ * @return ezcGraphColor New color
+ */
+ public function darken( $value )
+ {
+ $color = clone $this;
+
+ $value = 1 - $value;
+ $color->red = min( 255, max( 0, (int) round( $this->red * $value ) ) );
+ $color->green = min( 255, max( 0, (int) round( $this->green * $value ) ) );
+ $color->blue = min( 255, max( 0, (int) round( $this->blue * $value ) ) );
+
+ return $color;
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/colors/linear_gradient.php b/include/ezcomponents/Graph/src/colors/linear_gradient.php
new file mode 100644
index 000000000..f6fc07f06
--- /dev/null
+++ b/include/ezcomponents/Graph/src/colors/linear_gradient.php
@@ -0,0 +1,147 @@
+properties['startColor'] = $startColor;
+ $this->properties['endColor'] = $endColor;
+ $this->properties['startPoint'] = $startPoint;
+ $this->properties['endPoint'] = $endPoint;
+ }
+
+ /**
+ * __set
+ *
+ * @param mixed $propertyName
+ * @param mixed $propertyValue
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'startPoint':
+ if ( !$propertyValue instanceof ezcGraphCoordinate )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' );
+ }
+ else
+ {
+ $this->properties['startPoint'] = $propertyValue;
+ }
+ break;
+ case 'endPoint':
+ if ( !$propertyValue instanceof ezcGraphCoordinate )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' );
+ }
+ else
+ {
+ $this->properties['endPoint'] = $propertyValue;
+ }
+ break;
+ case 'startColor':
+ $this->properties['startColor'] = ezcGraphColor::create( $propertyValue );
+ break;
+ case 'endColor':
+ $this->properties['endColor'] = ezcGraphColor::create( $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * __get
+ *
+ * @param mixed $propertyName
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return mixed
+ * @ignore
+ */
+ public function __get( $propertyName )
+ {
+ switch ( $propertyName )
+ {
+ case 'red':
+ case 'green':
+ case 'blue':
+ case 'alpha':
+ // Fallback to native color
+ return $this->properties['startColor']->$propertyName;
+ default:
+ if ( isset( $this->properties[$propertyName] ) )
+ {
+ return $this->properties[$propertyName];
+ }
+ else
+ {
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ }
+ }
+ }
+
+ /**
+ * Returns a unique string representation for the gradient.
+ *
+ * @access public
+ * @return void
+ */
+ public function __toString()
+ {
+ return sprintf( 'LinearGradient_%d_%d_%d_%d_%02x%02x%02x%02x_%02x%02x%02x%02x',
+ $this->properties['startPoint']->x,
+ $this->properties['startPoint']->y,
+ $this->properties['endPoint']->x,
+ $this->properties['endPoint']->y,
+ $this->properties['startColor']->red,
+ $this->properties['startColor']->green,
+ $this->properties['startColor']->blue,
+ $this->properties['startColor']->alpha,
+ $this->properties['endColor']->red,
+ $this->properties['endColor']->green,
+ $this->properties['endColor']->blue,
+ $this->properties['endColor']->alpha
+ );
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/colors/radial_gradient.php b/include/ezcomponents/Graph/src/colors/radial_gradient.php
new file mode 100644
index 000000000..ef0a911a7
--- /dev/null
+++ b/include/ezcomponents/Graph/src/colors/radial_gradient.php
@@ -0,0 +1,173 @@
+properties['center'] = $center;
+ $this->properties['width'] = (float) $width;
+ $this->properties['height'] = (float) $height;
+ $this->properties['offset'] = 0;
+ $this->properties['startColor'] = $startColor;
+ $this->properties['endColor'] = $endColor;
+ }
+
+ /**
+ * __set
+ *
+ * @param mixed $propertyName
+ * @param mixed $propertyValue
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'center':
+ if ( !$propertyValue instanceof ezcGraphCoordinate )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' );
+ }
+ else
+ {
+ $this->properties['center'] = $propertyValue;
+ }
+ break;
+ case 'width':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue <= 0 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' );
+ }
+
+ $this->properties['width'] = (float) $propertyValue;
+ break;
+ case 'height':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue <= 0 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' );
+ }
+
+ $this->properties['height'] = (float) $propertyValue;
+ break;
+ case 'offset':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue < 0 ) ||
+ ( $propertyValue > 1 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' );
+ }
+
+ $this->properties['offset'] = $propertyValue;
+ break;
+ case 'startColor':
+ $this->properties['startColor'] = ezcGraphColor::create( $propertyValue );
+ break;
+ case 'endColor':
+ $this->properties['endColor'] = ezcGraphColor::create( $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * __get
+ *
+ * @param mixed $propertyName
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @return mixed
+ * @ignore
+ */
+ public function __get( $propertyName )
+ {
+ switch ( $propertyName )
+ {
+ case 'red':
+ case 'green':
+ case 'blue':
+ case 'alpha':
+ // Fallback to native color
+ return $this->properties['startColor']->$propertyName;
+ default:
+ if ( isset( $this->properties[$propertyName] ) )
+ {
+ return $this->properties[$propertyName];
+ }
+ else
+ {
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ }
+ }
+ }
+
+ /**
+ * Returns a unique string representation for the gradient.
+ *
+ * @access public
+ * @return void
+ */
+ public function __toString()
+ {
+ return sprintf( 'RadialGradient_%d_%d_%d_%d_%.2f_%02x%02x%02x%02x_%02x%02x%02x%02x',
+ $this->properties['center']->x,
+ $this->properties['center']->y,
+ $this->properties['width'],
+ $this->properties['height'],
+ $this->properties['offset'],
+ $this->properties['startColor']->red,
+ $this->properties['startColor']->green,
+ $this->properties['startColor']->blue,
+ $this->properties['startColor']->alpha,
+ $this->properties['endColor']->red,
+ $this->properties['endColor']->green,
+ $this->properties['endColor']->blue,
+ $this->properties['endColor']->alpha
+ );
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/data_container/base.php b/include/ezcomponents/Graph/src/data_container/base.php
new file mode 100644
index 000000000..57feae6ff
--- /dev/null
+++ b/include/ezcomponents/Graph/src/data_container/base.php
@@ -0,0 +1,225 @@
+chart = $chart;
+ }
+
+ /**
+ * Adds a dataset to the charts data
+ *
+ * @param string $name Name of dataset
+ * @param ezcGraphDataSet $dataSet
+ * @param mixed $values Values to create dataset with
+ * @throws ezcGraphTooManyDataSetExceptions
+ * If too many datasets are created
+ * @return ezcGraphDataSet
+ */
+ protected function addDataSet( $name, ezcGraphDataSet $dataSet )
+ {
+ $this->data[$name] = $dataSet;
+
+ $this->data[$name]->label = $name;
+ $this->data[$name]->palette = $this->chart->palette;
+ $this->data[$name]->displayType = $this->chart->getDefaultDisplayType();
+ }
+
+ /**
+ * Returns if the given offset exists.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key Identifier of dataset.
+ * @return bool True when the offset exists, otherwise false.
+ */
+ public function offsetExists( $key )
+ {
+ return isset( $this->data[$key] );
+ }
+
+ /**
+ * Returns the element with the given offset.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key Identifier of dataset.
+ * @return ezcGraphDataSet
+ *
+ * @throws ezcGraphNoSuchDataSetException
+ * If no dataset with identifier exists
+ */
+ public function offsetGet( $key )
+ {
+ if ( !isset( $this->data[$key] ) )
+ {
+ throw new ezcGraphNoSuchDataSetException( $key );
+ }
+
+ return $this->data[$key];
+ }
+
+ /**
+ * Set the element with the given offset.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key
+ * @param ezcGraphDataSet $value
+ * @return void
+ *
+ * @throws ezcBaseValueException
+ * If supplied value is not an ezcGraphDataSet
+ */
+ public function offsetSet( $key, $value )
+ {
+ if ( !$value instanceof ezcGraphDataSet )
+ {
+ throw new ezcBaseValueException( $key, $value, 'ezcGraphDataSet' );
+ }
+
+ return $this->addDataSet( $key, $value );
+ }
+
+ /**
+ * Unset the element with the given offset.
+ *
+ * This method is part of the ArrayAccess interface to allow access to the
+ * data of this object as if it was an array.
+ *
+ * @param string $key
+ * @return void
+ */
+ public function offsetUnset( $key )
+ {
+ if ( !isset( $this->data[$key] ) )
+ {
+ throw new ezcGraphNoSuchDataSetException( $key );
+ }
+
+ unset( $this->data[$key] );
+ }
+
+ /**
+ * Returns the currently selected dataset.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return ezcGraphDataSet The currently selected dataset.
+ */
+ public function current()
+ {
+ return current( $this->data );
+ }
+
+ /**
+ * Returns the next dataset and selects it or false on the last dataset.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return mixed ezcGraphDataSet if the next dataset exists, or false.
+ */
+ public function next()
+ {
+ return next( $this->data );
+ }
+
+ /**
+ * Returns the key of the currently selected dataset.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return int The key of the currently selected dataset.
+ */
+ public function key()
+ {
+ return key( $this->data );
+ }
+
+ /**
+ * Returns if the current dataset is valid.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return bool If the current dataset is valid
+ */
+ public function valid()
+ {
+ return ( current( $this->data ) !== false );
+ }
+
+ /**
+ * Selects the very first dataset and returns it.
+ * This method is part of the Iterator interface to allow access to the
+ * datasets of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return ezcGraphDataSet The very first dataset.
+ */
+ public function rewind()
+ {
+ return reset( $this->data );
+ }
+
+ /**
+ * Returns the number of datasets in the row.
+ *
+ * This method is part of the Countable interface to allow the usage of
+ * PHP's count() function to check how many datasets exist.
+ *
+ * @return int Number of datasets.
+ */
+ public function count()
+ {
+ return count( $this->data );
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/data_container/single.php b/include/ezcomponents/Graph/src/data_container/single.php
new file mode 100644
index 000000000..02dff2c16
--- /dev/null
+++ b/include/ezcomponents/Graph/src/data_container/single.php
@@ -0,0 +1,51 @@
+data ) >= 1 &&
+ !isset( $this->data[$name] ) )
+ {
+ throw new ezcGraphTooManyDataSetsExceptions( $name );
+ }
+ else
+ {
+ parent::addDataSet( $name, $dataSet );
+
+ // Resette palette color counter
+ $this->chart->palette->resetColorCounter();
+
+ // Colorize each data element
+ foreach ( $this->data[$name] as $label => $value )
+ {
+ $this->data[$name]->color[$label] = $this->chart->palette->dataSetColor;
+ }
+ }
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/datasets/array.php b/include/ezcomponents/Graph/src/datasets/array.php
new file mode 100644
index 000000000..531763c66
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/array.php
@@ -0,0 +1,71 @@
+createFromArray( $data );
+ parent::__construct();
+ }
+
+ /**
+ * setData
+ *
+ * Can handle data provided through an array or iterator.
+ *
+ * @param array|Iterator $data
+ * @access public
+ * @return void
+ */
+ protected function createFromArray( $data = array() )
+ {
+ if ( !is_array( $data ) &&
+ !( $data instanceof Traversable ) )
+ {
+ throw new ezcGraphInvalidArrayDataSourceException( $data );
+ }
+
+ $this->data = array();
+ foreach ( $data as $key => $value )
+ {
+ $this->data[$key] = $value;
+ }
+
+ if ( !count( $this->data ) )
+ {
+ throw new ezcGraphInvalidDataException( 'Data sets should contain some values.' );
+ }
+ }
+
+ /**
+ * Returns the number of elements in this dataset
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count( $this->data );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/datasets/average.php b/include/ezcomponents/Graph/src/datasets/average.php
new file mode 100644
index 000000000..d9f63a969
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/average.php
@@ -0,0 +1,359 @@
+mixed)
+ */
+ protected $properties;
+
+ /**
+ * Constructor
+ *
+ * @param ezcGraphDataSet $dataset Dataset to interpolate
+ * @param int $order Maximum order of interpolating polynom
+ * @return void
+ * @ignore
+ */
+ public function __construct( ezcGraphDataSet $dataset, $order = 3 )
+ {
+ parent::__construct();
+
+ $this->properties['resolution'] = 100;
+ $this->properties['polynomOrder'] = (int) $order;
+
+ $this->source = $dataset;
+ }
+
+ /**
+ * Options write access
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If Option could not be found
+ * @throws ezcBaseValueException
+ * If value is out of range
+ * @param mixed $propertyName Option name
+ * @param mixed $propertyValue Option value;
+ * @return mixed
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName ) {
+ case 'polynomOrder':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue < 0 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 0' );
+ }
+
+ $this->properties['polynomOrder'] = (int) $propertyValue;
+ $this->polynom = false;
+ break;
+ case 'resolution':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue < 1 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 1' );
+ }
+
+ $this->properties['resolution'] = (int) $propertyValue;
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * Property get access.
+ * Simply returns a given option.
+ *
+ * @param string $propertyName The name of the option to get.
+ * @return mixed The option value.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ */
+ public function __get( $propertyName )
+ {
+ if ( array_key_exists( $propertyName, $this->properties ) )
+ {
+ return $this->properties[$propertyName];
+ }
+ return parent::__get( $propertyName );
+ }
+
+ /**
+ * Build the polynom based on the given points.
+ *
+ * @return void
+ */
+ protected function buildPolynom()
+ {
+ $points = array();
+
+ foreach ( $this->source as $key => $value )
+ {
+ if ( !is_numeric( $key ) )
+ {
+ throw new ezcGraphDatasetAverageInvalidKeysException();
+ }
+
+ if ( ( $this->min === false ) || ( $this->min > $key ) )
+ {
+ $this->min = (float) $key;
+ }
+
+ if ( ( $this->max === false ) || ( $this->max < $key ) )
+ {
+ $this->max = (float) $key;
+ }
+
+ $points[] = new ezcGraphCoordinate( (float) $key, (float) $value );
+ }
+
+ // Build transposed and normal Matrix out of coordiantes
+ $a = new ezcGraphMatrix( count( $points ), $this->polynomOrder + 1 );
+ $b = new ezcGraphMatrix( count( $points ), 1 );
+
+ for ( $i = 0; $i <= $this->properties['polynomOrder']; ++$i )
+ {
+ foreach ( $points as $nr => $point )
+ {
+ $a->set( $nr, $i, pow( $point->x, $i ) );
+ $b->set( $nr, 0, $point->y );
+ }
+ }
+
+ $at = clone $a;
+ $at->transpose();
+
+ $left = $at->multiply( $a );
+ $right = $at->multiply( $b );
+
+ $this->polynom = $left->solveNonlinearEquatation( $right );
+ }
+
+ /**
+ * Returns a polynom of the defined order witch matches the datapoints
+ * using the least squares algorithm.
+ *
+ * @return ezcGraphPolynom Polynom
+ */
+ public function getPolynom()
+ {
+ if ( $this->polynom === false )
+ {
+ $this->buildPolynom();
+ }
+
+ return $this->polynom;
+ }
+
+ /**
+ * Get the x coordinate for the current position
+ *
+ * @param int $position Position
+ * @return float x coordinate
+ */
+ protected function getKey()
+ {
+ $polynom = $this->getPolynom();
+ return $this->min +
+ ( $this->max - $this->min ) / $this->resolution * $this->position;
+ }
+
+ /**
+ * Returns true if the given datapoint exists
+ * Allows isset() using ArrayAccess.
+ *
+ * @param string $key The key of the datapoint to get.
+ * @return bool Wether the key exists.
+ */
+ public function offsetExists( $key )
+ {
+ $polynom = $this->getPolynom();
+ return ( ( $key >= $this->min ) && ( $key <= $this->max ) );
+ }
+
+ /**
+ * Returns the value for the given datapoint
+ * Get an datapoint value by ArrayAccess.
+ *
+ * @param string $key The key of the datapoint to get.
+ * @return float The datapoint value.
+ */
+ public function offsetGet( $key )
+ {
+ $polynom = $this->getPolynom();
+ return $polynom->evaluate( $key );
+ }
+
+ /**
+ * Throws a ezcBasePropertyPermissionException because single datapoints
+ * cannot be set in average datasets.
+ *
+ * @param string $key The kex of a datapoint to set.
+ * @param float $value The value for the datapoint.
+ * @throws ezcBasePropertyPermissionException
+ * Always, because access is readonly.
+ * @return void
+ */
+ public function offsetSet( $key, $value )
+ {
+ throw new ezcBasePropertyPermissionException( $key, ezcBasePropertyPermissionException::READ );
+ }
+
+ /**
+ * Returns the currently selected datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return string The currently selected datapoint.
+ */
+ final public function current()
+ {
+ $polynom = $this->getPolynom();
+ return $polynom->evaluate( $this->getKey() );
+ }
+
+ /**
+ * Returns the next datapoint and selects it or false on the last datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return float datapoint if it exists, or false.
+ */
+ final public function next()
+ {
+ if ( ++$this->position >= $this->resolution )
+ {
+ return false;
+ }
+ else
+ {
+ return $this->current();
+ }
+ }
+
+ /**
+ * Returns the key of the currently selected datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return string The key of the currently selected datapoint.
+ */
+ final public function key()
+ {
+ return (string) $this->getKey();
+ }
+
+ /**
+ * Returns if the current datapoint is valid.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return bool If the current datapoint is valid
+ */
+ final public function valid()
+ {
+ $polynom = $this->getPolynom();
+
+ if ( $this->min >= $this->max )
+ {
+ return false;
+ }
+
+ return ( ( $this->getKey() >= $this->min ) && ( $this->getKey() <= $this->max ) );
+ }
+
+ /**
+ * Selects the very first datapoint and returns it.
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return float The very first datapoint.
+ */
+ final public function rewind()
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * Returns the number of elements in this dataset
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->resolution;
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/datasets/base.php b/include/ezcomponents/Graph/src/datasets/base.php
new file mode 100644
index 000000000..916640b42
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/base.php
@@ -0,0 +1,295 @@
+properties['label'] = new ezcGraphDataSetStringProperty( $this );
+ $this->properties['color'] = new ezcGraphDataSetColorProperty( $this );
+ $this->properties['symbol'] = new ezcGraphDataSetIntProperty( $this );
+ $this->properties['lineThickness'] = new ezcGraphDataSetIntProperty( $this );
+ $this->properties['highlight'] = new ezcGraphDataSetBooleanProperty( $this );
+ $this->properties['highlightValue'] = new ezcGraphDataSetStringProperty( $this );
+ $this->properties['displayType'] = new ezcGraphDataSetIntProperty( $this );
+ $this->properties['url'] = new ezcGraphDataSetStringProperty( $this );
+
+ $this->properties['xAxis'] = new ezcGraphDataSetAxisProperty( $this );
+ $this->properties['yAxis'] = new ezcGraphDataSetAxisProperty( $this );
+
+ $this->properties['highlight']->default = false;
+ }
+
+ /**
+ * Options write access
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If Option could not be found
+ * @throws ezcBaseValueException
+ * If value is out of range
+ * @param mixed $propertyName Option name
+ * @param mixed $propertyValue Option value;
+ * @return void
+ * @ignore
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case 'hilight':
+ $propertyName = 'highlight';
+ case 'label':
+ case 'url':
+ case 'color':
+ case 'symbol':
+ case 'lineThickness':
+ case 'highlight':
+ case 'highlightValue':
+ case 'displayType':
+ case 'xAxis':
+ case 'yAxis':
+ $this->properties[$propertyName]->default = $propertyValue;
+ break;
+
+ case 'palette':
+ $this->palette = $propertyValue;
+ $this->color->default = $this->palette->dataSetColor;
+ $this->symbol->default = $this->palette->dataSetSymbol;
+ break;
+
+ default:
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ break;
+ }
+ }
+
+ /**
+ * Property get access.
+ * Simply returns a given option.
+ *
+ * @param string $propertyName The name of the option to get.
+ * @return mixed The option value.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ */
+ public function __get( $propertyName )
+ {
+ if ( array_key_exists( $propertyName, $this->properties ) )
+ {
+ return $this->properties[$propertyName];
+ }
+ else
+ {
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ }
+ }
+
+ /**
+ * Returns true if the given datapoint exists
+ * Allows isset() using ArrayAccess.
+ *
+ * @param string $key The key of the datapoint to get.
+ * @return bool Wether the key exists.
+ */
+ public function offsetExists( $key )
+ {
+ return isset( $this->data[$key] );
+ }
+
+ /**
+ * Returns the value for the given datapoint
+ * Get an datapoint value by ArrayAccess.
+ *
+ * @param string $key The key of the datapoint to get.
+ * @return float The datapoint value.
+ */
+ public function offsetGet( $key )
+ {
+ return $this->data[$key];
+ }
+
+ /**
+ * Sets the value for a datapoint.
+ * Sets an datapoint using ArrayAccess.
+ *
+ * @param string $key The kex of a datapoint to set.
+ * @param float $value The value for the datapoint.
+ * @return void
+ */
+ public function offsetSet( $key, $value )
+ {
+ $this->data[$key] = (float) $value;
+ }
+
+ /**
+ * Unset an option.
+ * Unsets an option using ArrayAccess.
+ *
+ * @param string $key The options to unset.
+ * @return void
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ * @throws ezcBaseValueException
+ * If a the value for a property is out of range.
+ */
+ public function offsetUnset( $key )
+ {
+ unset( $this->data[$key] );
+ }
+
+ /**
+ * Returns the currently selected datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return string The currently selected datapoint.
+ */
+ public function current()
+ {
+ $keys = array_keys( $this->data );
+ if ( !isset( $this->current ) )
+ {
+ $this->current = 0;
+ }
+
+ return $this->data[$keys[$this->current]];
+ }
+
+ /**
+ * Returns the next datapoint and selects it or false on the last datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return float datapoint if it exists, or false.
+ */
+ public function next()
+ {
+ $keys = array_keys( $this->data );
+ if ( ++$this->current >= count( $keys ) )
+ {
+ return false;
+ }
+ else
+ {
+ return $this->data[$keys[$this->current]];
+ }
+ }
+
+ /**
+ * Returns the key of the currently selected datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return string The key of the currently selected datapoint.
+ */
+ public function key()
+ {
+ $keys = array_keys( $this->data );
+ return $keys[$this->current];
+ }
+
+ /**
+ * Returns if the current datapoint is valid.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return bool If the current datapoint is valid
+ */
+ public function valid()
+ {
+ $keys = array_keys( $this->data );
+ return isset( $keys[$this->current] );
+ }
+
+ /**
+ * Selects the very first datapoint and returns it.
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return float The very first datapoint.
+ */
+ public function rewind()
+ {
+ $this->current = 0;
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/datasets/numeric.php b/include/ezcomponents/Graph/src/datasets/numeric.php
new file mode 100644
index 000000000..0bdebbed9
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/numeric.php
@@ -0,0 +1,287 @@
+mixed)
+ */
+ protected $properties;
+
+ /**
+ * Constructor
+ *
+ * @param float $start Start value for x axis values of function
+ * @param float $end End value for x axis values of function
+ * @param callback $callback Callback function
+ * @return void
+ * @ignore
+ */
+ public function __construct( $start = null, $end = null, $callback = null )
+ {
+ parent::__construct();
+
+ $this->properties['start'] = null;
+ $this->properties['end'] = null;
+ $this->properties['callback'] = null;
+
+ if ( $start !== null )
+ {
+ $this->start = $start;
+ }
+
+ if ( $end !== null )
+ {
+ $this->end = $end;
+ }
+
+ if ( $callback !== null )
+ {
+ $this->callback = $callback;
+ }
+
+ $this->properties['resolution'] = 100;
+ }
+
+ /**
+ * Options write access
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If Option could not be found
+ * @throws ezcBaseValueException
+ * If value is out of range
+ * @param mixed $propertyName Option name
+ * @param mixed $propertyValue Option value;
+ * @return mixed
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName ) {
+ case 'resolution':
+ if ( !is_numeric( $propertyValue ) ||
+ ( $propertyValue < 1 ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'int > 1' );
+ }
+
+ $this->properties['resolution'] = (int) $propertyValue;
+ break;
+ case 'start':
+ case 'end':
+ if ( !is_numeric( $propertyValue ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'float' );
+ }
+
+ $this->properties[$propertyName] = (float) $propertyValue;
+ break;
+ case 'callback':
+ if ( !is_callable( $propertyValue ) )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, 'callback' );
+ }
+
+ $this->properties[$propertyName] = $propertyValue;
+ break;
+ default:
+ parent::__set( $propertyName, $propertyValue );
+ break;
+ }
+ }
+
+ /**
+ * Property get access.
+ * Simply returns a given option.
+ *
+ * @param string $propertyName The name of the option to get.
+ * @return mixed The option value.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If a the value for the property options is not an instance of
+ */
+ public function __get( $propertyName )
+ {
+ if ( array_key_exists( $propertyName, $this->properties ) )
+ {
+ return $this->properties[$propertyName];
+ }
+ return parent::__get( $propertyName );
+ }
+
+ /**
+ * Get the x coordinate for the current position
+ *
+ * @param int $position Position
+ * @return float x coordinate
+ */
+ protected function getKey()
+ {
+ return $this->start +
+ ( $this->end - $this->start ) / $this->resolution * $this->position;
+ }
+
+ /**
+ * Returns true if the given datapoint exists
+ * Allows isset() using ArrayAccess.
+ *
+ * @param string $key The key of the datapoint to get.
+ * @return bool Wether the key exists.
+ */
+ public function offsetExists( $key )
+ {
+ return ( ( $key >= $this->start ) && ( $key <= $this->end ) );
+ }
+
+ /**
+ * Returns the value for the given datapoint
+ * Get an datapoint value by ArrayAccess.
+ *
+ * @param string $key The key of the datapoint to get.
+ * @return float The datapoint value.
+ */
+ public function offsetGet( $key )
+ {
+ return call_user_func( $this->callback, $key );
+ }
+
+ /**
+ * Throws a ezcBasePropertyPermissionException because single datapoints
+ * cannot be set in average datasets.
+ *
+ * @param string $key The kex of a datapoint to set.
+ * @param float $value The value for the datapoint.
+ * @throws ezcBasePropertyPermissionException
+ * Always, because access is readonly.
+ * @return void
+ */
+ public function offsetSet( $key, $value )
+ {
+ throw new ezcBasePropertyPermissionException( $key, ezcBasePropertyPermissionException::READ );
+ }
+
+ /**
+ * Returns the currently selected datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return string The currently selected datapoint.
+ */
+ final public function current()
+ {
+ return call_user_func( $this->callback, $this->getKey() );
+ }
+
+ /**
+ * Returns the next datapoint and selects it or false on the last datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return float datapoint if it exists, or false.
+ */
+ final public function next()
+ {
+ if ( $this->start === $this->end )
+ {
+ throw new ezcGraphDatasetAverageInvalidKeysException();
+ }
+
+ if ( ++$this->position >= $this->resolution )
+ {
+ return false;
+ }
+ else
+ {
+ return $this->current();
+ }
+ }
+
+ /**
+ * Returns the key of the currently selected datapoint.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return string The key of the currently selected datapoint.
+ */
+ final public function key()
+ {
+ return (string) $this->getKey();
+ }
+
+ /**
+ * Returns if the current datapoint is valid.
+ *
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return bool If the current datapoint is valid
+ */
+ final public function valid()
+ {
+ return ( ( $this->getKey() >= $this->start ) && ( $this->getKey() <= $this->end ) );
+ }
+
+ /**
+ * Selects the very first datapoint and returns it.
+ * This method is part of the Iterator interface to allow access to the
+ * datapoints of this row by iterating over it like an array (e.g. using
+ * foreach).
+ *
+ * @return float The very first datapoint.
+ */
+ final public function rewind()
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * Returns the number of elements in this dataset
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return $this->resolution + 1;
+ }
+}
+?>
diff --git a/include/ezcomponents/Graph/src/datasets/property/axis.php b/include/ezcomponents/Graph/src/datasets/property/axis.php
new file mode 100644
index 000000000..c0b18333d
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/property/axis.php
@@ -0,0 +1,56 @@
+
diff --git a/include/ezcomponents/Graph/src/datasets/property/boolean.php b/include/ezcomponents/Graph/src/datasets/property/boolean.php
new file mode 100644
index 000000000..bf2bc1153
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/property/boolean.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/Graph/src/datasets/property/color.php b/include/ezcomponents/Graph/src/datasets/property/color.php
new file mode 100644
index 000000000..2d84592a0
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/property/color.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/Graph/src/datasets/property/integer.php b/include/ezcomponents/Graph/src/datasets/property/integer.php
new file mode 100644
index 000000000..cd9517df8
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/property/integer.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/Graph/src/datasets/property/string.php b/include/ezcomponents/Graph/src/datasets/property/string.php
new file mode 100644
index 000000000..f4f98c4ba
--- /dev/null
+++ b/include/ezcomponents/Graph/src/datasets/property/string.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/Graph/src/driver/cairo.php b/include/ezcomponents/Graph/src/driver/cairo.php
new file mode 100644
index 000000000..8110aa3ca
--- /dev/null
+++ b/include/ezcomponents/Graph/src/driver/cairo.php
@@ -0,0 +1,1010 @@
+
+ * $graph = new ezcGraphPieChart();
+ * $graph->background->color = '#FFFFFFFF';
+ * $graph->title = 'Access statistics';
+ * $graph->legend = false;
+ *
+ * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ * 'Mozilla' => 19113,
+ * 'Explorer' => 10917,
+ * 'Opera' => 1464,
+ * 'Safari' => 652,
+ * 'Konqueror' => 474,
+ * ) );
+ *
+ * $graph->renderer = new ezcGraphRenderer3d();
+ * $graph->renderer->options->pieChartShadowSize = 10;
+ * $graph->renderer->options->pieChartGleam = .5;
+ * $graph->renderer->options->dataBorder = false;
+ * $graph->renderer->options->pieChartHeight = 16;
+ * $graph->renderer->options->legendSymbolGleam = .5;
+ *
+ * // Use cairo driver
+ * $graph->driver = new ezcGraphCairoDriver();
+ *
+ * $graph->render( 400, 200, 'tutorial_driver_cairo.png' );
+ *
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphCairoDriver extends ezcGraphDriver
+{
+ /**
+ * Surface for cairo
+ *
+ * @var resource
+ */
+ protected $surface;
+
+ /**
+ * Current cairo context.
+ *
+ * @var resource
+ */
+ protected $context;
+
+ /**
+ * List of strings to draw
+ * array ( array(
+ * 'text' => array( 'strings' ),
+ * 'options' => ezcGraphFontOptions,
+ * )
+ *
+ * @var array
+ */
+ protected $strings = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'cairo_wrapper' );
+ $this->options = new ezcGraphCairoDriverOptions( $options );
+ }
+
+ /**
+ * Initilize cairo surface
+ *
+ * Initilize cairo surface from values provided in the options object, if
+ * is has not been already initlized.
+ *
+ * @return void
+ */
+ protected function initiliazeSurface()
+ {
+ // Immediatly exit, if surface already exists
+ if ( $this->surface !== null )
+ {
+ return;
+ }
+
+ $this->surface = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32,
+ $this->options->width,
+ $this->options->height
+ );
+
+ $this->context = cairo_create( $this->surface );
+ cairo_set_line_width( $this->context, 1 );
+ }
+
+ /**
+ * Get SVG style definition
+ *
+ * Returns a string with SVG style definitions created from color,
+ * fillstatus and line thickness.
+ *
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @param float $thickness Line thickness.
+ * @return string Formatstring
+ */
+ protected function getStyle( ezcGraphColor $color, $filled = true, $thickness = 1. )
+ {
+ switch ( true )
+ {
+ case $color instanceof ezcGraphLinearGradient:
+ $pattern = cairo_pattern_create_linear(
+ $color->startPoint->x, $color->startPoint->y,
+ $color->endPoint->x, $color->endPoint->y
+ );
+
+ cairo_pattern_add_color_stop_rgba (
+ $pattern,
+ 0,
+ $color->startColor->red / 255,
+ $color->startColor->green / 255,
+ $color->startColor->blue / 255,
+ 1 - $color->startColor->alpha / 255
+ );
+
+ cairo_pattern_add_color_stop_rgba (
+ $pattern,
+ 1,
+ $color->endColor->red / 255,
+ $color->endColor->green / 255,
+ $color->endColor->blue / 255,
+ 1 - $color->endColor->alpha / 255
+ );
+
+ cairo_set_source( $this->context, $pattern );
+ cairo_fill( $this->context );
+ break;
+
+ case $color instanceof ezcGraphRadialGradient:
+ $pattern = cairo_pattern_create_radial(
+ 0, 0, 0,
+ 0, 0, 1
+ );
+
+ cairo_pattern_add_color_stop_rgba (
+ $pattern,
+ 0,
+ $color->startColor->red / 255,
+ $color->startColor->green / 255,
+ $color->startColor->blue / 255,
+ 1 - $color->startColor->alpha / 255
+ );
+
+ cairo_pattern_add_color_stop_rgba (
+ $pattern,
+ 1,
+ $color->endColor->red / 255,
+ $color->endColor->green / 255,
+ $color->endColor->blue / 255,
+ 1 - $color->endColor->alpha / 255
+ );
+
+ // Scale pattern, and move it to the correct position
+ $matrix = cairo_matrix_multiply(
+ $move = cairo_matrix_create_translate( -$color->center->x, -$color->center->y ),
+ $scale = cairo_matrix_create_scale( 1 / $color->width, 1 / $color->height )
+ );
+ cairo_pattern_set_matrix( $pattern, $matrix );
+
+ cairo_set_source( $this->context, $pattern );
+ cairo_fill( $this->context );
+ break;
+ default:
+ cairo_set_source_rgba(
+ $this->context,
+ $color->red / 255,
+ $color->green / 255,
+ $color->blue / 255,
+ 1 - $color->alpha / 255
+ );
+ break;
+ }
+
+ // Set line width
+ cairo_set_line_width( $this->context, $thickness );
+
+ // Set requested fill state for context
+ if ( $filled )
+ {
+ cairo_fill_preserve( $this->context );
+ }
+ }
+
+ /**
+ * Draws a single polygon.
+ *
+ * @param array $points Point array
+ * @param ezcGraphColor $color Polygon color
+ * @param mixed $filled Filled
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. )
+ {
+ $this->initiliazeSurface();
+
+ $path = cairo_new_path( $this->context );
+
+ $lastPoint = end( $points );
+ cairo_move_to( $this->context, $lastPoint->x, $lastPoint->y );
+
+ foreach ( $points as $point )
+ {
+ cairo_line_to( $this->context, $point->x, $point->y );
+ }
+
+ cairo_close_path( $this->context );
+
+ $this->getStyle( $color, $filled, $thickness );
+ cairo_stroke( $this->context );
+
+ return $points;
+ }
+
+ /**
+ * Draws a line
+ *
+ * @param ezcGraphCoordinate $start Start point
+ * @param ezcGraphCoordinate $end End point
+ * @param ezcGraphColor $color Line color
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. )
+ {
+ $this->initiliazeSurface();
+
+ $path = cairo_new_path( $this->context );
+
+ cairo_move_to( $this->context, $start->x, $start->y );
+ cairo_line_to( $this->context, $end->x, $end->y );
+
+ $this->getStyle( $color, false, $thickness );
+ cairo_stroke( $this->context );
+
+ return array( $start, $end );
+ }
+
+ /**
+ * Returns boundings of text depending on the available font extension
+ *
+ * @param float $size Textsize
+ * @param ezcGraphFontOptions $font Font
+ * @param string $text Text
+ * @return ezcGraphBoundings Boundings of text
+ */
+ protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text )
+ {
+ cairo_select_font_face( $this->context, $font->name, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
+ cairo_set_font_size( $this->context, $size );
+ $extents = cairo_text_extents( $this->context, $text );
+
+ return new ezcGraphBoundings(
+ 0,
+ 0,
+ $extents['width'],
+ $extents['height']
+ );
+ }
+
+ /**
+ * Writes text in a box of desired size
+ *
+ * @param string $string Text
+ * @param ezcGraphCoordinate $position Top left position
+ * @param float $width Width of text box
+ * @param float $height Height of text box
+ * @param int $align Alignement of text
+ * @param ezcGraphRotation $rotation
+ * @return void
+ */
+ public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null )
+ {
+ $this->initiliazeSurface();
+
+ $padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 );
+
+ $width -= $padding * 2;
+ $height -= $padding * 2;
+ $textPosition = new ezcGraphCoordinate(
+ $position->x + $padding,
+ $position->y + $padding
+ );
+
+ // Try to get a font size for the text to fit into the box
+ $maxSize = min( $height, $this->options->font->maxFontSize );
+ $result = false;
+ for ( $size = $maxSize; $size >= $this->options->font->minFontSize; )
+ {
+ $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size );
+ if ( is_array( $result ) )
+ {
+ break;
+ }
+ $size = ( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : floor( $newsize ) );
+ }
+
+ if ( !is_array( $result ) )
+ {
+ if ( ( $height >= $this->options->font->minFontSize ) &&
+ ( $this->options->autoShortenString ) )
+ {
+ $result = $this->tryFitShortenedString( $string, $position, $width, $height, $size = $this->options->font->minFontSize );
+ }
+ else
+ {
+ throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height );
+ }
+ }
+
+ $this->options->font->minimalUsedFont = $size;
+ $this->strings[] = array(
+ 'text' => $result,
+ 'position' => $textPosition,
+ 'width' => $width,
+ 'height' => $height,
+ 'align' => $align,
+ 'font' => $this->options->font,
+ 'rotation' => $rotation,
+ );
+
+ return array(
+ clone $position,
+ new ezcGraphCoordinate( $position->x + $width, $position->y ),
+ new ezcGraphCoordinate( $position->x + $width, $position->y + $height ),
+ new ezcGraphCoordinate( $position->x, $position->y + $height ),
+ );
+ }
+
+ /**
+ * Render text depending of font type and available font extensions
+ *
+ * @param string $id
+ * @param string $text
+ * @param string $font
+ * @param ezcGraphColor $color
+ * @param ezcGraphCoordinate $position
+ * @param float $size
+ * @param float $rotation
+ * @return void
+ */
+ protected function renderText( $text, $font, ezcGraphColor $color, ezcGraphCoordinate $position, $size, $rotation = null )
+ {
+ cairo_select_font_face( $this->context, $font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL );
+ cairo_set_font_size( $this->context, $size );
+
+ // Store current state of context
+ cairo_save( $this->context );
+ cairo_move_to( $this->context, 0, 0 );
+
+ if ( $rotation !== null )
+ {
+ // Move to the center
+ cairo_translate( $this->context,
+ $rotation->getCenter()->x,
+ $rotation->getCenter()->y
+ );
+ // Rotate around text center
+ cairo_rotate( $this->context,
+ deg2rad( $rotation->getRotation() )
+ );
+ // Center the text
+ cairo_translate( $this->context,
+ $position->x - $rotation->getCenter()->x,
+ $position->y - $rotation->getCenter()->y - $size * .15
+ );
+ } else {
+ cairo_translate( $this->context,
+ $position->x,
+ $position->y - $size * .15
+ );
+ }
+
+ cairo_new_path( $this->context );
+ $this->getStyle( $color, true );
+ cairo_show_text( $this->context, $text );
+ cairo_stroke( $this->context );
+
+ // Restore state of context
+ cairo_restore( $this->context );
+ }
+
+ /**
+ * Draw all collected texts
+ *
+ * The texts are collected and their maximum possible font size is
+ * calculated. This function finally draws the texts on the image, this
+ * delayed drawing has two reasons:
+ *
+ * 1) This way the text strings are always on top of the image, what
+ * results in better readable texts
+ * 2) The maximum possible font size can be calculated for a set of texts
+ * with the same font configuration. Strings belonging to one chart
+ * element normally have the same font configuration, so that all texts
+ * belonging to one element will have the same font size.
+ *
+ * @access protected
+ * @return void
+ */
+ protected function drawAllTexts()
+ {
+ $this->initiliazeSurface();
+
+ foreach ( $this->strings as $text )
+ {
+ $size = $text['font']->minimalUsedFont;
+
+ $completeHeight = count( $text['text'] ) * $size + ( count( $text['text'] ) - 1 ) * $this->options->lineSpacing;
+
+ // Calculate y offset for vertical alignement
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::BOTTOM ):
+ $yOffset = $text['height'] - $completeHeight;
+ break;
+ case ( $text['align'] & ezcGraph::MIDDLE ):
+ $yOffset = ( $text['height'] - $completeHeight ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::TOP ):
+ default:
+ $yOffset = 0;
+ break;
+ }
+
+ $padding = $text['font']->padding + $text['font']->borderWidth / 2;
+ if ( $this->options->font->minimizeBorder === true )
+ {
+ // Calculate maximum width of text rows
+ $width = false;
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ $boundings = $this->getTextBoundings( $size, $text['font'], $string );
+ if ( ( $width === false) || ( $boundings->width > $width ) )
+ {
+ $width = $boundings->width;
+ }
+ }
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $xOffset = ( $text['width'] - $width ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $xOffset = $text['width'] - $width;
+ break;
+ case ( $text['align'] & ezcGraph::LEFT ):
+ default:
+ $xOffset = 0;
+ break;
+ }
+
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding + $xOffset,
+ $text['position']->y - $padding + $yOffset
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $xOffset + $width,
+ $text['position']->y - $padding + $yOffset
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $xOffset + $width,
+ $text['position']->y + $padding * 2 + $yOffset + $completeHeight
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding + $xOffset,
+ $text['position']->y + $padding * 2 + $yOffset + $completeHeight
+ ),
+ );
+ }
+ else
+ {
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding,
+ $text['position']->y - $padding
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $text['width'],
+ $text['position']->y - $padding
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $text['width'],
+ $text['position']->y + $padding * 2 + $text['height']
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding,
+ $text['position']->y + $padding * 2 + $text['height']
+ ),
+ );
+ }
+
+ if ( $text['rotation'] !== null )
+ {
+ foreach ( $borderPolygonArray as $nr => $point )
+ {
+ $borderPolygonArray[$nr] = $text['rotation']->transformCoordinate( $point );
+ }
+ }
+
+ if ( $text['font']->background !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->background,
+ true
+ );
+ }
+
+ if ( $text['font']->border !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->border,
+ false,
+ $text['font']->borderWidth
+ );
+ }
+
+ // Render text with evaluated font size
+ $completeString = '';
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ $completeString .= $string;
+ $boundings = $this->getTextBoundings( $size, $text['font'], $string );
+ $text['position']->y += $size;
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::LEFT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x,
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( $text['width'] - $boundings->width ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( ( $text['width'] - $boundings->width ) / 2 ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ }
+
+ // Optionally draw text shadow
+ if ( $text['font']->textShadow === true )
+ {
+ $this->renderText(
+ $string,
+ $text['font']->name,
+ $text['font']->textShadowColor,
+ new ezcGraphCoordinate(
+ $position->x + $text['font']->textShadowOffset,
+ $position->y + $text['font']->textShadowOffset
+ ),
+ $size,
+ $text['rotation']
+ );
+ }
+
+ // Finally draw text
+ $this->renderText(
+ $string,
+ $text['font']->name,
+ $text['font']->color,
+ $position,
+ $size,
+ $text['rotation']
+ );
+
+ $text['position']->y += $size * $this->options->lineSpacing;
+ }
+ }
+ }
+
+ /**
+ * Draws a sector of cirlce
+ *
+ * @param ezcGraphCoordinate $center Center of circle
+ * @param mixed $width Width
+ * @param mixed $height Height
+ * @param mixed $startAngle Start angle of circle sector
+ * @param mixed $endAngle End angle of circle sector
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled;
+ * @return void
+ */
+ public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ $this->initiliazeSurface();
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ cairo_save( $this->context );
+
+ // Draw circular arc path
+ $path = cairo_new_path( $this->context );
+ cairo_translate( $this->context,
+ $center->x,
+ $center->y
+ );
+ cairo_scale( $this->context,
+ 1, $height / $width
+ );
+
+ cairo_move_to( $this->context, 0, 0 );
+ cairo_arc( $this->context,
+ 0., 0.,
+ $width / 2,
+ deg2rad( $startAngle ),
+ deg2rad( $endAngle )
+ );
+ cairo_line_to( $this->context, 0, 0 );
+
+ cairo_restore( $this->context );
+ $this->getStyle( $color, $filled );
+ cairo_stroke( $this->context );
+
+ // Create polygon array to return
+ $polygonArray = array( $center );
+ for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
+ );
+ }
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
+ );
+
+ return $polygonArray;
+ }
+
+ /**
+ * Draws a circular arc consisting of several minor steps on the bounding
+ * lines.
+ *
+ * @param ezcGraphCoordinate $center
+ * @param mixed $width
+ * @param mixed $height
+ * @param mixed $size
+ * @param mixed $startAngle
+ * @param mixed $endAngle
+ * @param ezcGraphColor $color
+ * @param bool $filled
+ * @return string Element id
+ */
+ protected function simulateCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled )
+ {
+ for (
+ $tmpAngle = min( ceil ( $startAngle / 180 ) * 180, $endAngle );
+ $tmpAngle <= $endAngle;
+ $tmpAngle = min( ceil ( $startAngle / 180 + 1 ) * 180, $endAngle ) )
+ {
+ $path = cairo_new_path( $this->context );
+ cairo_move_to( $this->context,
+ $center->x + cos( deg2rad( $startAngle ) ) * $width / 2,
+ $center->y + sin( deg2rad( $startAngle ) ) * $height / 2
+ );
+
+ // @TODO: Use cairo_curve_to()
+ for(
+ $angle = $startAngle;
+ $angle <= $tmpAngle;
+ $angle = min( $angle + $this->options->circleResolution, $tmpAngle ) )
+ {
+ cairo_line_to( $this->context,
+ $center->x + cos( deg2rad( $angle ) ) * $width / 2,
+ $center->y + sin( deg2rad( $angle ) ) * $height / 2 + $size
+ );
+
+ if ( $angle === $tmpAngle )
+ {
+ break;
+ }
+ }
+
+ for(
+ $angle = $tmpAngle;
+ $angle >= $startAngle;
+ $angle = max( $angle - $this->options->circleResolution, $startAngle ) )
+ {
+ cairo_line_to( $this->context,
+ $center->x + cos( deg2rad( $angle ) ) * $width / 2,
+ $center->y + sin( deg2rad( $angle ) ) * $height / 2
+ );
+
+ if ( $angle === $startAngle )
+ {
+ break;
+ }
+ }
+
+ cairo_close_path( $this->context );
+ $this->getStyle( $color, $filled );
+ cairo_stroke( $this->context );
+
+ $startAngle = $tmpAngle;
+ if ( $tmpAngle === $endAngle )
+ {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Draws a circular arc
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param integer $width Width of ellipse
+ * @param integer $height Height of ellipse
+ * @param integer $size Height of border
+ * @param float $startAngle Starting angle of circle sector
+ * @param float $endAngle Ending angle of circle sector
+ * @param ezcGraphColor $color Color of Border
+ * @param bool $filled
+ * @return void
+ */
+ public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ $this->initiliazeSurface();
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $color, $filled );
+
+ if ( ( $this->options->shadeCircularArc !== false ) &&
+ $filled )
+ {
+ $gradient = new ezcGraphLinearGradient(
+ new ezcGraphCoordinate(
+ $center->x - $width,
+ $center->y
+ ),
+ new ezcGraphCoordinate(
+ $center->x + $width,
+ $center->y
+ ),
+ ezcGraphColor::fromHex( '#FFFFFF' )->transparent( $this->options->shadeCircularArc * 1.5 ),
+ ezcGraphColor::fromHex( '#000000' )->transparent( $this->options->shadeCircularArc * 1.5 )
+ );
+
+ $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $gradient, $filled );
+ }
+
+ // Create polygon array to return
+ $polygonArray = array();
+ for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
+ );
+ }
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
+ );
+
+ for ( $angle = $endAngle; $angle > $startAngle; $angle -= $this->options->imageMapResolution )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ) + $size,
+ $center->y +
+ ( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
+ );
+ }
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ) + $size,
+ $center->y +
+ ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 )
+ );
+
+ return $polygonArray;
+ }
+
+ /**
+ * Draw circle
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param mixed $width Width of ellipse
+ * @param mixed $height height of ellipse
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @return void
+ */
+ public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true )
+ {
+ $this->initiliazeSurface();
+
+ cairo_save( $this->context );
+
+ // Draw circular arc path
+ $path = cairo_new_path( $this->context );
+ cairo_translate( $this->context,
+ $center->x,
+ $center->y
+ );
+ cairo_scale( $this->context,
+ 1, $height / $width
+ );
+
+ cairo_arc( $this->context,
+ 0., 0.,
+ $width / 2,
+ 0, 2 * M_PI
+ );
+
+ cairo_restore( $this->context );
+ $this->getStyle( $color, $filled );
+ cairo_stroke( $this->context );
+
+ // Create polygon array to return
+ $polygonArray = array();
+ for ( $angle = 0; $angle < ( 2 * M_PI ); $angle += deg2rad( $this->options->imageMapResolution ) )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( $angle ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( $angle ) * $height ) / 2 )
+ );
+ }
+
+ return $polygonArray;
+ }
+
+ /**
+ * Draw an image
+ *
+ * The image will be inlined in the SVG document using data URL scheme. For
+ * this the mime type and base64 encoded file content will be merged to
+ * URL.
+ *
+ * @param mixed $file Image file
+ * @param ezcGraphCoordinate $position Top left position
+ * @param mixed $width Width of image in destination image
+ * @param mixed $height Height of image in destination image
+ * @return void
+ */
+ public function drawImage( $file, ezcGraphCoordinate $position, $width, $height )
+ {
+ $this->initiliazeSurface();
+
+ // Ensure given bitmap is a PNG image
+ $data = getimagesize( $file );
+ if ( $data[2] !== IMAGETYPE_PNG )
+ {
+ throw new Exception( 'Cairo only has support for PNGs.' );
+ }
+
+ // Create new surface from given bitmap
+ $imageSurface = cairo_image_surface_create_from_png( $file );
+
+ // Create pattern from source image to be able to transform it
+ $pattern = cairo_pattern_create_for_surface( $imageSurface );
+
+ // Scale pattern to defined dimensions and move it to its destination position
+ $matrix = cairo_matrix_multiply(
+ $move = cairo_matrix_create_translate( -$position->x, -$position->y ),
+ $scale = cairo_matrix_create_scale( $data[0] / $width, $data[1] / $height )
+ );
+ cairo_pattern_set_matrix( $pattern, $matrix );
+
+ // Merge surfaces
+ cairo_set_source( $this->context, $pattern );
+ cairo_rectangle( $this->context, $position->x, $position->y, $width, $height );
+ cairo_fill( $this->context );
+ }
+
+ /**
+ * Return mime type for current image format
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return 'image/png';
+ }
+
+ /**
+ * Render image directly to output
+ *
+ * The method renders the image directly to the standard output. You
+ * normally do not want to use this function, because it makes it harder
+ * to proper cache the generated graphs.
+ *
+ * @return void
+ */
+ public function renderToOutput()
+ {
+ $this->drawAllTexts();
+
+ header( 'Content-Type: ' . $this->getMimeType() );
+
+ // Write to tmp file, echo and remove tmp file again.
+ $fileName = tempnam( '/tmp', 'ezc' );
+
+ // cairo_surface_write_to_png( $this->surface, $file );
+ cairo_surface_write_to_png( $this->surface, $fileName );
+ $contents = file_get_contents( $fileName );
+ unlink( $fileName );
+
+ // Directly echo contents
+ echo $contents;
+ }
+
+ /**
+ * Finally save image
+ *
+ * @param string $file Destination filename
+ * @return void
+ */
+ public function render( $file )
+ {
+ $this->drawAllTexts();
+ cairo_surface_write_to_png( $this->surface, $file );
+ }
+
+ /**
+ * Get resource of rendered result
+ *
+ * Return the resource of the rendered result. You should not use this
+ * method before you called either renderToOutput() or render(), as the
+ * image may not be completely rendered until then.
+ *
+ * This method returns an array, containing the surface and the context in
+ * a structure like:
+ *
+ * array(
+ * 'surface' => resource,
+ * 'context' => resource,
+ * )
+ *
+ *
+ * @return array
+ */
+ public function getResource()
+ {
+ return array(
+ 'surface' => $this->surface,
+ 'context' => $this->context,
+ );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/driver/flash.php b/include/ezcomponents/Graph/src/driver/flash.php
new file mode 100644
index 000000000..1ba5702e6
--- /dev/null
+++ b/include/ezcomponents/Graph/src/driver/flash.php
@@ -0,0 +1,972 @@
+
+ * $graph = new ezcGraphPieChart();
+ * $graph->title = 'Access statistics';
+ * $graph->legend = false;
+ *
+ * $graph->driver = new ezcGraphFlashDriver();
+ * $graph->options->font = 'tutorial_font.fdb';
+ *
+ * $graph->driver->options->compression = 7;
+ *
+ * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ * 'Mozilla' => 19113,
+ * 'Explorer' => 10917,
+ * 'Opera' => 1464,
+ * 'Safari' => 652,
+ * 'Konqueror' => 474,
+ * ) );
+ *
+ * $graph->render( 400, 200, 'tutorial_driver_flash.swf' );
+ *
+ *
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphFlashDriver extends ezcGraphDriver
+{
+ /**
+ * Flash movie
+ *
+ * @var SWFMovie
+ */
+ protected $movie;
+
+ /**
+ * Unique element id
+ *
+ * @var int
+ */
+ protected $id = 1;
+
+ /**
+ * Array with strings to draw later
+ *
+ * @var array
+ */
+ protected $strings = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'ming' );
+ $this->options = new ezcGraphFlashDriverOptions( $options );
+ }
+
+ /**
+ * Returns unique movie object as a parent canvas for all swf objects.
+ *
+ * @return SWFMovie
+ */
+ public function getDocument()
+ {
+ if ( $this->movie === null )
+ {
+ ming_setscale( 1.0 );
+ $this->movie = new SWFMovie();
+ $this->movie->setDimension( $this->modifyCoordinate( $this->options->width ), $this->modifyCoordinate( $this->options->height ) );
+ $this->movie->setRate( 1 );
+ $this->movie->setBackground( 255, 255, 255 );
+ }
+
+ return $this->movie;
+ }
+
+ /**
+ * Set the fill and line properties for a SWWFShape according to the
+ * given parameters.
+ *
+ * @param SWFShape $shape
+ * @param ezcGraphColor $color
+ * @param mixed $thickness
+ * @param mixed $filled
+ * @return void
+ */
+ protected function setShapeColor( SWFShape $shape, ezcGraphColor $color, $thickness, $filled )
+ {
+ if ( $filled )
+ {
+ switch ( true )
+ {
+ case ( $color instanceof ezcGraphLinearGradient ):
+ $gradient = new SWFGradient();
+ $gradient->addEntry(
+ 0,
+ $color->startColor->red,
+ $color->startColor->green,
+ $color->startColor->blue,
+ 255 - $color->startColor->alpha
+ );
+ $gradient->addEntry(
+ 1,
+ $color->endColor->red,
+ $color->endColor->green,
+ $color->endColor->blue,
+ 255 - $color->endColor->alpha
+ );
+
+ $fill = $shape->addFill( $gradient, SWFFILL_LINEAR_GRADIENT );
+
+ // Calculate desired length of gradient
+ $length = sqrt(
+ pow( $color->endPoint->x - $color->startPoint->x, 2 ) +
+ pow( $color->endPoint->y - $color->startPoint->y, 2 )
+ );
+
+ $fill->scaleTo( $this->modifyCoordinate( $length ) / 32768 , $this->modifyCoordinate( $length ) / 32768 );
+ $fill->rotateTo(
+ rad2deg( asin(
+ ( $color->endPoint->x - $color->startPoint->x ) / $length
+ ) + 180 )
+ );
+ $fill->moveTo(
+ $this->modifyCoordinate(
+ ( $color->startPoint->x + $color->endPoint->x ) / 2
+ ),
+ $this->modifyCoordinate(
+ ( $color->startPoint->y + $color->endPoint->y ) / 2
+ )
+ );
+
+ $shape->setLeftFill( $fill );
+ break;
+ case ( $color instanceof ezcGraphRadialGradient ):
+ $gradient = new SWFGradient();
+ $gradient->addEntry(
+ 0,
+ $color->startColor->red,
+ $color->startColor->green,
+ $color->startColor->blue,
+ 255 - $color->startColor->alpha
+ );
+ $gradient->addEntry(
+ 1,
+ $color->endColor->red,
+ $color->endColor->green,
+ $color->endColor->blue,
+ 255 - $color->endColor->alpha
+ );
+
+ $fill = $shape->addFill( $gradient, SWFFILL_RADIAL_GRADIENT );
+
+ $fill->scaleTo( $this->modifyCoordinate( $color->width ) / 32768, $this->modifyCoordinate( $color->height ) / 32768 );
+ $fill->moveTo( $this->modifyCoordinate( $color->center->x ), $this->modifyCoordinate( $color->center->y ) );
+
+ $shape->setLeftFill( $fill );
+ break;
+ default:
+ $fill = $shape->addFill( $color->red, $color->green, $color->blue, 255 - $color->alpha );
+ $shape->setLeftFill( $fill );
+ break;
+ }
+ }
+ else
+ {
+ $shape->setLine( $this->modifyCoordinate( $thickness ), $color->red, $color->green, $color->blue, 255 - $color->alpha );
+ }
+ }
+
+ /**
+ * Modifies a coordinate value, as flash usally uses twips instead of
+ * pixels for a higher solution, as it only accepts integer values.
+ *
+ * @param float $pointValue
+ * @return float
+ */
+ protected function modifyCoordinate( $pointValue )
+ {
+ return $pointValue * 10;
+ }
+
+ /**
+ * Demodifies a coordinate value, as flash usally uses twips instead of
+ * pixels for a higher solution, as it only accepts integer values.
+ *
+ * @param float $pointValue
+ * @return float
+ */
+ protected function deModifyCoordinate( $pointValue )
+ {
+ return $pointValue / 10;
+ }
+
+ /**
+ * Draws a single polygon.
+ *
+ * @param array $points Point array
+ * @param ezcGraphColor $color Polygon color
+ * @param mixed $filled Filled
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. )
+ {
+ $movie = $this->getDocument();
+
+ if ( !$filled )
+ {
+ // The middle of the border is on the outline of a polygon in ming,
+ // fix that:
+ try
+ {
+ $points = $this->reducePolygonSize( $points, $thickness / 2 );
+ }
+ catch ( ezcGraphReducementFailedException $e )
+ {
+ return false;
+ }
+ }
+
+ $shape = new SWFShape();
+
+ $this->setShapeColor( $shape, $color, $thickness, $filled );
+
+ $lastPoint = end( $points );
+ $shape->movePenTo( $this->modifyCoordinate( $lastPoint->x ), $this->modifyCoordinate( $lastPoint->y ) );
+
+ foreach ( $points as $point )
+ {
+ $shape->drawLineTo( $this->modifyCoordinate( $point->x ), $this->modifyCoordinate( $point->y ) );
+ }
+
+ $object = $movie->add( $shape );
+ $object->setName( $id = 'ezcGraphPolygon_' . $this->id++ );
+
+ return $id;
+ }
+
+ /**
+ * Draws a line
+ *
+ * @param ezcGraphCoordinate $start Start point
+ * @param ezcGraphCoordinate $end End point
+ * @param ezcGraphColor $color Line color
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. )
+ {
+ $movie = $this->getDocument();
+
+ $shape = new SWFShape();
+
+ $this->setShapeColor( $shape, $color, $thickness, false );
+
+ $shape->movePenTo( $this->modifyCoordinate( $start->x ), $this->modifyCoordinate( $start->y ) );
+ $shape->drawLineTo( $this->modifyCoordinate( $end->x ), $this->modifyCoordinate( $end->y ) );
+
+ $object = $movie->add( $shape );
+ $object->setName( $id = 'ezcGraphLine_' . $this->id++ );
+
+ return $id;
+ }
+
+ /**
+ * Returns boundings of text depending on the available font extension
+ *
+ * @param float $size Textsize
+ * @param ezcGraphFontOptions $font Font
+ * @param string $text Text
+ * @return ezcGraphBoundings Boundings of text
+ */
+ protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text )
+ {
+ $t = new SWFText();
+ $t->setFont( new SWFFont( $font->path ) );
+ $t->setHeight( $size );
+
+ $boundings = new ezcGraphBoundings( 0, 0, $t->getWidth( $text ), $size );
+
+ return $boundings;
+ }
+
+ /**
+ * Writes text in a box of desired size
+ *
+ * @param string $string Text
+ * @param ezcGraphCoordinate $position Top left position
+ * @param float $width Width of text box
+ * @param float $height Height of text box
+ * @param int $align Alignement of text
+ * @param ezcGraphRotation $rotation
+ * @return void
+ */
+ public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null )
+ {
+ $padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 );
+
+ $width = $this->modifyCoordinate( $width - $padding * 2 );
+ $height = $this->modifyCoordinate( $height - $padding * 2 );
+ $position = new ezcGraphCoordinate(
+ $this->modifyCoordinate( $position->x + $padding ),
+ $this->modifyCoordinate( $position->y + $padding )
+ );
+
+ // Try to get a font size for the text to fit into the box
+ $maxSize = $this->modifyCoordinate( min( $height, $this->options->font->maxFontSize ) );
+ $minSize = $this->modifyCoordinate( $this->options->font->minFontSize );
+ $result = false;
+ for ( $size = $maxSize; $size >= $minSize; )
+ {
+ $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size );
+ if ( is_array( $result ) )
+ {
+ break;
+ }
+ $size = $this->deModifyCoordinate( $size );
+ $size = $this->modifyCoordinate( floor( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : $newsize ) );
+ }
+
+ if ( !is_array( $result ) )
+ {
+ if ( ( $height >= $this->options->font->minFontSize ) &&
+ ( $this->options->autoShortenString ) )
+ {
+ $result = $this->tryFitShortenedString( $string, $position, $width, $height, $size = $this->modifyCoordinate( $this->options->font->minFontSize ) );
+ }
+ else
+ {
+ throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height );
+ }
+ }
+
+
+ $this->options->font->minimalUsedFont = $this->deModifyCoordinate( $size );
+
+ $this->strings[] = array(
+ 'text' => $result,
+ 'id' => $id = 'ezcGraphTextBox_' . $this->id++,
+ 'position' => $position,
+ 'width' => $width,
+ 'height' => $height,
+ 'align' => $align,
+ 'font' => $this->options->font,
+ 'rotation' => $rotation,
+ );
+
+ return $id;
+ }
+
+ /**
+ * Render text depending of font type and available font extensions
+ *
+ * @param string $id
+ * @param string $text
+ * @param string $chars
+ * @param int $type
+ * @param string $path
+ * @param ezcGraphColor $color
+ * @param ezcGraphCoordinate $position
+ * @param float $size
+ * @param float $rotation
+ * @return void
+ */
+ protected function renderText( $id, $text, $chars, $type, $path, ezcGraphColor $color, ezcGraphCoordinate $position, $size, $rotation = null )
+ {
+ $movie = $this->getDocument();
+
+ $tb = new SWFTextField( SWFTEXTFIELD_NOEDIT );
+ $tb->setFont( new SWFFont( $path ) );
+ $tb->setHeight( $size );
+ $tb->setColor( $color->red, $color->green, $color->blue, 255 - $color->alpha );
+ $tb->addString( $text );
+ $tb->addChars( $chars );
+
+ $object = $movie->add( $tb );
+ $object->rotate(
+ ( $rotation !== null ? -$rotation->getRotation() : 0 )
+ );
+ $object->moveTo(
+ $position->x +
+ ( $rotation === null ? 0 : $this->modifyCoordinate( $rotation->get( 0, 2 ) ) ),
+ $position->y -
+ $size * ( 1 + $this->options->lineSpacing ) +
+ ( $rotation === null ? 0 : $this->modifyCoordinate( $rotation->get( 1, 2 ) ) )
+ );
+ $object->setName( $id );
+ }
+
+ /**
+ * Draw all collected texts
+ *
+ * The texts are collected and their maximum possible font size is
+ * calculated. This function finally draws the texts on the image, this
+ * delayed drawing has two reasons:
+ *
+ * 1) This way the text strings are always on top of the image, what
+ * results in better readable texts
+ * 2) The maximum possible font size can be calculated for a set of texts
+ * with the same font configuration. Strings belonging to one chart
+ * element normally have the same font configuration, so that all texts
+ * belonging to one element will have the same font size.
+ *
+ * @access protected
+ * @return void
+ */
+ protected function drawAllTexts()
+ {
+ // Iterate over all strings to collect used chars per font
+ $chars = array();
+ foreach ( $this->strings as $text )
+ {
+ $completeString = '';
+ foreach ( $text['text'] as $line )
+ {
+ $completeString .= implode( ' ', $line );
+ }
+
+ // Collect chars for each font
+ if ( !isset( $chars[$text['font']->path] ) )
+ {
+ $chars[$text['font']->path] = $completeString;
+ }
+ else
+ {
+ $chars[$text['font']->path] .= $completeString;
+ }
+ }
+
+ foreach ( $this->strings as $text )
+ {
+ $size = $this->modifyCoordinate( $text['font']->minimalUsedFont );
+
+ $completeHeight = count( $text['text'] ) * $size + ( count( $text['text'] ) - 1 ) * $this->options->lineSpacing;
+
+ // Calculate y offset for vertical alignement
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::BOTTOM ):
+ $yOffset = $text['height'] - $completeHeight;
+ break;
+ case ( $text['align'] & ezcGraph::MIDDLE ):
+ $yOffset = ( $text['height'] - $completeHeight ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::TOP ):
+ default:
+ $yOffset = 0;
+ break;
+ }
+
+ $padding = $text['font']->padding + $text['font']->borderWidth / 2;
+ if ( $this->options->font->minimizeBorder === true )
+ {
+ // Calculate maximum width of text rows
+ $width = false;
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ $boundings = $this->getTextBoundings( $size, $text['font'], $string );
+ if ( ( $width === false) || ( $boundings->width > $width ) )
+ {
+ $width = $boundings->width;
+ }
+ }
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::LEFT ):
+ $xOffset = 0;
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $xOffset = ( $text['width'] - $width ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $xOffset = $text['width'] - $width;
+ break;
+ }
+
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x - $padding + $xOffset ),
+ $this->deModifyCoordinate( $text['position']->y - $padding + $yOffset )
+ ),
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $xOffset + $width ),
+ $this->deModifyCoordinate( $text['position']->y - $padding + $yOffset )
+ ),
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $xOffset + $width ),
+ $this->deModifyCoordinate( $text['position']->y + $padding * 2 + $yOffset + $completeHeight )
+ ),
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x - $padding + $xOffset ),
+ $this->deModifyCoordinate( $text['position']->y + $padding * 2 + $yOffset + $completeHeight )
+ ),
+ );
+ }
+ else
+ {
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x - $padding ),
+ $this->deModifyCoordinate( $text['position']->y - $padding )
+ ),
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $text['width'] ),
+ $this->deModifyCoordinate( $text['position']->y - $padding )
+ ),
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x + $padding * 2 + $text['width'] ),
+ $this->deModifyCoordinate( $text['position']->y + $padding * 2 + $text['height'] )
+ ),
+ new ezcGraphCoordinate(
+ $this->deModifyCoordinate( $text['position']->x - $padding ),
+ $this->deModifyCoordinate( $text['position']->y + $padding * 2 + $text['height'] )
+ ),
+ );
+ }
+
+ if ( $text['rotation'] !== null )
+ {
+ foreach ( $borderPolygonArray as $nr => $point )
+ {
+ $borderPolygonArray[$nr] = $text['rotation']->transformCoordinate( $point );
+ }
+ }
+
+ if ( $text['font']->background !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->background,
+ true
+ );
+ }
+
+ if ( $text['font']->border !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->border,
+ false,
+ $text['font']->borderWidth
+ );
+ }
+
+ // Render text with evaluated font size
+ $completeString = '';
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ $completeString .= $string;
+ $boundings = $this->getTextBoundings( $size, $text['font'], $string );
+ $text['position']->y += $size;
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::LEFT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x,
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( $text['width'] - $boundings->width ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( ( $text['width'] - $boundings->width ) / 2 ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ }
+
+ // Optionally draw text shadow
+ if ( $text['font']->textShadow === true )
+ {
+ $this->renderText(
+ $text['id'],
+ $string,
+ $chars[$text['font']->path],
+ $text['font']->type,
+ $text['font']->path,
+ $text['font']->textShadowColor,
+ new ezcGraphCoordinate(
+ $position->x + $this->modifyCoordinate( $text['font']->textShadowOffset ),
+ $position->y + $this->modifyCoordinate( $text['font']->textShadowOffset )
+ ),
+ $size,
+ $text['rotation']
+ );
+ }
+
+ // Finally draw text
+ $this->renderText(
+ $text['id'],
+ $string,
+ $chars[$text['font']->path],
+ $text['font']->type,
+ $text['font']->path,
+ $text['font']->color,
+ $position,
+ $size,
+ $text['rotation']
+ );
+
+ $text['position']->y += $size * $this->options->lineSpacing;
+ }
+ }
+ }
+
+ /**
+ * Draws a sector of cirlce
+ *
+ * @param ezcGraphCoordinate $center Center of circle
+ * @param mixed $width Width
+ * @param mixed $height Height
+ * @param mixed $startAngle Start angle of circle sector
+ * @param mixed $endAngle End angle of circle sector
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @return void
+ */
+ public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ $movie = $this->getDocument();
+
+ $shape = new SWFShape();
+ $this->setShapeColor( $shape, $color, 1, $filled );
+
+ if ( !$filled )
+ {
+ try
+ {
+ $reduced = $this->reduceEllipseSize( $center, $width, $height, $startAngle, $endAngle, .5 );
+ }
+ catch ( ezcGraphReducementFailedException $e )
+ {
+ return false;
+ }
+
+ $startAngle = $reduced['startAngle'];
+ $endAngle = $reduced['endAngle'];
+
+ $width -= 1;
+ $height -= 1;
+ }
+
+ $shape->movePenTo( $this->modifyCoordinate( $center->x ), $this->modifyCoordinate( $center->y ) );
+
+ // @TODO: User SWFShape::curveTo
+ for(
+ $angle = $startAngle;
+ $angle <= $endAngle;
+ $angle = min( $angle + $this->options->circleResolution, $endAngle ) )
+ {
+ $shape->drawLineTo(
+ $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ),
+ $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 )
+ );
+
+ if ( $angle === $endAngle )
+ {
+ break;
+ }
+ }
+
+ $shape->drawLineTo(
+ $this->modifyCoordinate( $center->x ),
+ $this->modifyCoordinate( $center->y )
+ );
+
+ $object = $movie->add( $shape );
+ $object->setName( $id = 'ezcGraphCircleSector_' . $this->id++ );
+
+ return $id;
+ }
+
+ /**
+ * Draws a circular arc consisting of several minor steps on the bounding
+ * lines.
+ *
+ * @param ezcGraphCoordinate $center
+ * @param mixed $width
+ * @param mixed $height
+ * @param mixed $size
+ * @param mixed $startAngle
+ * @param mixed $endAngle
+ * @param ezcGraphColor $color
+ * @param bool $filled
+ * @return string Element id
+ */
+ protected function simulateCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled )
+ {
+ $movie = $this->getDocument();
+ $id = 'ezcGraphCircularArc_' . $this->id++;
+
+ for (
+ $tmpAngle = min( ceil ( $startAngle / 180 ) * 180, $endAngle );
+ $tmpAngle <= $endAngle;
+ $tmpAngle = min( ceil ( $startAngle / 180 + 1 ) * 180, $endAngle ) )
+ {
+ $shape = new SWFShape();
+ $this->setShapeColor( $shape, $color, 1, $filled );
+
+ $shape->movePenTo(
+ $this->modifyCoordinate( $center->x + cos( deg2rad( $startAngle ) ) * $width / 2 ),
+ $this->modifyCoordinate( $center->y + sin( deg2rad( $startAngle ) ) * $height / 2 )
+ );
+
+ // @TODO: Use SWFShape::curveTo
+ for(
+ $angle = $startAngle;
+ $angle <= $tmpAngle;
+ $angle = min( $angle + $this->options->circleResolution, $tmpAngle ) )
+ {
+ $shape->drawLineTo(
+ $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ),
+ $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 + $size )
+ );
+
+ if ( $angle === $tmpAngle )
+ {
+ break;
+ }
+ }
+
+ for(
+ $angle = $tmpAngle;
+ $angle >= $startAngle;
+ $angle = max( $angle - $this->options->circleResolution, $startAngle ) )
+ {
+ $shape->drawLineTo(
+ $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ),
+ $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 )
+ );
+
+ if ( $angle === $startAngle )
+ {
+ break;
+ }
+ }
+
+ $object = $movie->add( $shape );
+ $object->setName( $id );
+
+ $startAngle = $tmpAngle;
+ if ( $tmpAngle === $endAngle )
+ {
+ break;
+ }
+ }
+
+ return $id;
+ }
+
+ /**
+ * Draws a circular arc
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param integer $width Width of ellipse
+ * @param integer $height Height of ellipse
+ * @param integer $size Height of border
+ * @param float $startAngle Starting angle of circle sector
+ * @param float $endAngle Ending angle of circle sector
+ * @param ezcGraphColor $color Color of Border
+ * @param bool $filled
+ * @return void
+ */
+ public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ $id = $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $color, $filled );
+
+ if ( ( $this->options->shadeCircularArc !== false ) &&
+ $filled )
+ {
+ $gradient = new ezcGraphLinearGradient(
+ new ezcGraphCoordinate(
+ $center->x - $width,
+ $center->y
+ ),
+ new ezcGraphCoordinate(
+ $center->x + $width,
+ $center->y
+ ),
+ ezcGraphColor::fromHex( '#FFFFFF' )->transparent( $this->options->shadeCircularArc * 1.5 ),
+ ezcGraphColor::fromHex( '#000000' )->transparent( $this->options->shadeCircularArc * 1.5 )
+ );
+
+ $this->simulateCircularArc( $center, $width, $height, $size, $startAngle, $endAngle, $gradient, $filled );
+ }
+
+ return $id;
+ }
+
+ /**
+ * Draw circle
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param mixed $width Width of ellipse
+ * @param mixed $height height of ellipse
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @return void
+ */
+ public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true )
+ {
+ $movie = $this->getDocument();
+
+ $shape = new SWFShape();
+ $this->setShapeColor( $shape, $color, 1, $filled );
+
+ // Reduce size
+ if ( !$filled )
+ {
+ $width -= 1;
+ $height -= 1;
+ }
+
+ $shape->movePenTo(
+ $this->modifyCoordinate( $center->x + $width / 2 ),
+ $this->modifyCoordinate( $center->y )
+ );
+
+ // @TODO: User SWFShape::curveTo
+ for ( $angle = $this->options->circleResolution; $angle < 360; $angle += $this->options->circleResolution )
+ {
+ $shape->drawLineTo(
+ $this->modifyCoordinate( $center->x + cos( deg2rad( $angle ) ) * $width / 2 ),
+ $this->modifyCoordinate( $center->y + sin( deg2rad( $angle ) ) * $height / 2 )
+ );
+ }
+
+ $shape->drawLineTo(
+ $this->modifyCoordinate( $center->x + $width / 2 ),
+ $this->modifyCoordinate( $center->y )
+ );
+
+ $object = $movie->add( $shape );
+ $object->setName( $id = 'ezcGraphCircle_' . $this->id++ );
+
+ return $id;
+ }
+
+ /**
+ * Draw an image
+ *
+ * The image will be inlined in the SVG document using data URL scheme. For
+ * this the mime type and base64 encoded file content will be merged to
+ * URL.
+ *
+ * @param mixed $file Image file
+ * @param ezcGraphCoordinate $position Top left position
+ * @param float $width Width of image in destination image
+ * @param float $height Height of image in destination image
+ * @return void
+ */
+ public function drawImage( $file, ezcGraphCoordinate $position, $width, $height )
+ {
+ $movie = $this->getDocument();
+
+ $imageData = getimagesize( $file );
+ if ( ( $imageData[2] !== IMAGETYPE_JPEG ) && ( $imageData[2] !== IMAGETYPE_PNG ) )
+ {
+ throw new ezcGraphFlashBitmapTypeException( $imageData[2] );
+ }
+
+ // Try to create a new SWFBitmap object from provided file
+ $bitmap = new SWFBitmap( fopen( $file, 'rb' ) );
+
+ // Add the image to the movie
+ $object = $this->movie->add( $bitmap );
+
+ // Image size is calculated on the base of a tick size of 20, so
+ // that we need to transform this, to our tick size.
+ $factor = $this->modifyCoordinate( 1 ) / 20;
+ $object->scale( $factor, $factor );
+
+ // Scale by ratio of requested and original image size
+ $object->scale(
+ $width / $imageData[0],
+ $height / $imageData[1]
+ );
+
+ // Move object to the right position
+ $object->moveTo(
+ $this->modifyCoordinate( $position->x ),
+ $this->modifyCoordinate( $position->y )
+ );
+
+ // Create, set and return unique ID
+ $object->setName( $id = 'ezcGraphImage_'. $this->id++ );
+ return $id;
+ }
+
+ /**
+ * Return mime type for current image format
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return 'application/x-shockwave-flash';
+ }
+
+ /**
+ * Finally save image
+ *
+ * @param string $file Destination filename
+ * @return void
+ */
+ public function render( $file )
+ {
+ $this->drawAllTexts();
+ $movie = $this->getDocument();
+ $movie->save( $file, $this->options->compression );
+ }
+
+ /**
+ * Get resource of rendered result
+ *
+ * Return the resource of the rendered result. You should not use this
+ * method before you called either renderToOutput() or render(), as the
+ * image may not be completely rendered until then.
+ *
+ * @return SWFMovie
+ */
+ public function getResource()
+ {
+ return $this->movie;
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/driver/gd.php b/include/ezcomponents/Graph/src/driver/gd.php
new file mode 100644
index 000000000..397136eb3
--- /dev/null
+++ b/include/ezcomponents/Graph/src/driver/gd.php
@@ -0,0 +1,1209 @@
+
+ * $graph = new ezcGraphPieChart();
+ * $graph->palette = new ezcGraphPaletteEzGreen();
+ * $graph->title = 'Access statistics';
+ * $graph->legend = false;
+ *
+ * $graph->driver = new ezcGraphGdDriver();
+ * $graph->options->font = 'tutorial_font.ttf';
+ *
+ * // Generate a Jpeg with lower quality. The default settings result in a image
+ * // with better quality.
+ * //
+ * // The reduction of the supersampling to 1 will result in no anti aliasing of
+ * // the image. JPEG is not the optimal format for grapics, PNG is far better for
+ * // this kind of images.
+ * $graph->driver->options->supersampling = 1;
+ * $graph->driver->options->jpegQuality = 100;
+ * $graph->driver->options->imageFormat = IMG_JPEG;
+ *
+ * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ * 'Mozilla' => 19113,
+ * 'Explorer' => 10917,
+ * 'Opera' => 1464,
+ * 'Safari' => 652,
+ * 'Konqueror' => 474,
+ * ) );
+ *
+ * $graph->render( 400, 200, 'tutorial_dirver_gd.jpg' );
+ *
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphGdDriver extends ezcGraphDriver
+{
+
+ /**
+ * Image resource
+ *
+ * @var resource
+ */
+ protected $image;
+
+ /**
+ * Array with image files to draw
+ *
+ * @var array
+ */
+ protected $preProcessImages = array();
+
+ /**
+ * List of strings to draw
+ * array ( array(
+ * 'text' => array( 'strings' ),
+ * 'options' => ezcGraphFontOptions,
+ * )
+ *
+ * @var array
+ */
+ protected $strings = array();
+
+ /**
+ * Contains resources for already loaded ps fonts.
+ * array(
+ * path => resource
+ * )
+ *
+ * @var array
+ */
+ protected $psFontResources = array();
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'gd' );
+ $this->options = new ezcGraphGdDriverOptions( $options );
+ }
+
+ /**
+ * Returns the image resource to draw on.
+ *
+ * If no resource exists the image will be created. The size of the
+ * returned image depends on the supersampling factor and the size of the
+ * chart.
+ *
+ * @return resource
+ */
+ protected function getImage()
+ {
+ if ( !isset( $this->image ) )
+ {
+ $this->image = imagecreatetruecolor(
+ $this->supersample( $this->options->width ),
+ $this->supersample( $this->options->height )
+ );
+
+ // Default to a transparent white background
+ $bgColor = imagecolorallocatealpha( $this->image, 255, 255, 255, 127 );
+ imagealphablending( $this->image, true );
+ imagesavealpha( $this->image, true );
+ imagefill( $this->image, 1, 1, $bgColor );
+
+ imagesetthickness(
+ $this->image,
+ $this->options->supersampling
+ );
+ }
+
+ return $this->image;
+ }
+
+ /**
+ * Allocates a color
+ *
+ * This function tries to allocate the requested color. If the color
+ * already exists in the imaga it will be reused.
+ *
+ * @param ezcGraphColor $color
+ * @return int Color index
+ */
+ protected function allocate( ezcGraphColor $color )
+ {
+ $image = $this->getImage();
+
+ if ( $color->alpha > 0 )
+ {
+ $fetched = imagecolorexactalpha( $image, $color->red, $color->green, $color->blue, $color->alpha / 2 );
+ if ( $fetched < 0 )
+ {
+ $fetched = imagecolorallocatealpha( $image, $color->red, $color->green, $color->blue, $color->alpha / 2 );
+ }
+ return $fetched;
+ }
+ else
+ {
+ $fetched = imagecolorexact( $image, $color->red, $color->green, $color->blue );
+ if ( $fetched < 0 )
+ {
+ $fetched = imagecolorallocate( $image, $color->red, $color->green, $color->blue );
+ }
+ return $fetched;
+ }
+ }
+
+ /**
+ * Creates an image resource from an image file
+ *
+ * @param string $file Filename
+ * @return resource Image
+ */
+ protected function imageCreateFrom( $file )
+ {
+ $data = getimagesize( $file );
+
+ switch ( $data[2] )
+ {
+ case 1:
+ return array(
+ 'width' => $data[0],
+ 'height' => $data[1],
+ 'image' => imagecreatefromgif( $file )
+ );
+ case 2:
+ return array(
+ 'width' => $data[0],
+ 'height' => $data[1],
+ 'image' => imagecreatefromjpeg( $file )
+ );
+ case 3:
+ return array(
+ 'width' => $data[0],
+ 'height' => $data[1],
+ 'image' => imagecreatefrompng( $file )
+ );
+ default:
+ throw new ezcGraphGdDriverUnsupportedImageTypeException( $data[2] );
+ }
+ }
+
+ /**
+ * Supersamples a single coordinate value.
+ *
+ * Applies supersampling to a single coordinate value.
+ *
+ * @param float $value Coordinate value
+ * @return float Supersampled coordinate value
+ */
+ protected function supersample( $value )
+ {
+ $mod = (int) floor( $this->options->supersampling / 2 );
+ return $value * $this->options->supersampling - $mod;
+ }
+
+ /**
+ * Draws a single polygon.
+ *
+ * @param array $points Point array
+ * @param ezcGraphColor $color Polygon color
+ * @param mixed $filled Filled
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. )
+ {
+ $image = $this->getImage();
+
+ $drawColor = $this->allocate( $color );
+
+ // Create point array
+ $pointCount = count( $points );
+ $pointArray = array();
+ for ( $i = 0; $i < $pointCount; ++$i )
+ {
+ $pointArray[] = $this->supersample( $points[$i]->x );
+ $pointArray[] = $this->supersample( $points[$i]->y );
+ }
+
+ // Draw polygon
+ if ( $filled )
+ {
+ imagefilledpolygon( $image, $pointArray, $pointCount, $drawColor );
+ }
+ else
+ {
+ imagepolygon( $image, $pointArray, $pointCount, $drawColor );
+ }
+
+ return $points;
+ }
+
+ /**
+ * Draws a line
+ *
+ * @param ezcGraphCoordinate $start Start point
+ * @param ezcGraphCoordinate $end End point
+ * @param ezcGraphColor $color Line color
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. )
+ {
+ $image = $this->getImage();
+
+ $drawColor = $this->allocate( $color );
+
+ imagesetthickness(
+ $this->image,
+ $this->options->supersampling * $thickness
+ );
+
+ imageline(
+ $image,
+ $this->supersample( $start->x ),
+ $this->supersample( $start->y ),
+ $this->supersample( $end->x ),
+ $this->supersample( $end->y ),
+ $drawColor
+ );
+
+ imagesetthickness(
+ $this->image,
+ $this->options->supersampling
+ );
+
+ return array();
+ }
+
+ /**
+ * Returns boundings of text depending on the available font extension
+ *
+ * @param float $size Textsize
+ * @param ezcGraphFontOptions $font Font
+ * @param string $text Text
+ * @return ezcGraphBoundings Boundings of text
+ */
+ protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text )
+ {
+ switch ( $font->type )
+ {
+ case ezcGraph::PS_FONT:
+ if ( !isset( $this->psFontResources[$font->path] ) )
+ {
+ $this->psFontResources[$font->path] = imagePsLoadFont( $font->path );
+ }
+
+ $boundings = imagePsBBox( $text, $this->psFontResources[$font->path], $size );
+ return new ezcGraphBoundings(
+ $boundings[0],
+ $boundings[1],
+ $boundings[2],
+ $boundings[3]
+ );
+ case ezcGraph::TTF_FONT:
+ switch ( true )
+ {
+ case ezcBaseFeatures::hasFunction( 'imageftbbox' ) && !$this->options->forceNativeTTF:
+ $boundings = imageFtBBox( $size, 0, $font->path, $text );
+ return new ezcGraphBoundings(
+ $boundings[0],
+ $boundings[1],
+ $boundings[4],
+ $boundings[5]
+ );
+ case ezcBaseFeatures::hasFunction( 'imagettfbbox' ):
+ $boundings = imageTtfBBox( $size, 0, $font->path, $text );
+ return new ezcGraphBoundings(
+ $boundings[0],
+ $boundings[1],
+ $boundings[4],
+ $boundings[5]
+ );
+ }
+ break;
+ }
+ }
+
+ /**
+ * Render text depending of font type and available font extensions
+ *
+ * @param resource $image Image resource
+ * @param string $text Text
+ * @param int $type Font type
+ * @param string $path Font path
+ * @param ezcGraphColor $color Font color
+ * @param ezcGraphCoordinate $position Position
+ * @param float $size Textsize
+ * @param ezcGraphRotation $rotation
+ *
+ * @return void
+ */
+ protected function renderText( $image, $text, $type, $path, ezcGraphColor $color, ezcGraphCoordinate $position, $size, ezcGraphRotation $rotation = null )
+ {
+ if ( $rotation !== null )
+ {
+ // Rotation is relative to top left point of text and not relative
+ // to the bounding coordinate system
+ $rotation = new ezcGraphRotation(
+ $rotation->getRotation(),
+ new ezcGraphCoordinate(
+ $rotation->getCenter()->x - $position->x,
+ $rotation->getCenter()->y - $position->y
+ )
+ );
+ }
+
+ switch ( $type )
+ {
+ case ezcGraph::PS_FONT:
+ imagePsText(
+ $image,
+ $text,
+ $this->psFontResources[$path],
+ $size,
+ $this->allocate( $color ),
+ 1,
+ $position->x +
+ ( $rotation === null ? 0 : $rotation->get( 0, 2 ) ),
+ $position->y +
+ ( $rotation === null ? 0 : $rotation->get( 1, 2 ) ),
+ 0,
+ 0,
+ ( $rotation === null ? 0 : -$rotation->getRotation() ),
+ 4
+ );
+ break;
+ case ezcGraph::TTF_FONT:
+ switch ( true )
+ {
+ case ezcBaseFeatures::hasFunction( 'imagefttext' ) && !$this->options->forceNativeTTF:
+ imageFtText(
+ $image,
+ $size,
+ ( $rotation === null ? 0 : -$rotation->getRotation() ),
+ $position->x +
+ ( $rotation === null ? 0 : $rotation->get( 0, 2 ) ),
+ $position->y +
+ ( $rotation === null ? 0 : $rotation->get( 1, 2 ) ),
+ $this->allocate( $color ),
+ $path,
+ $text
+ );
+ break;
+ case ezcBaseFeatures::hasFunction( 'imagettftext' ):
+ imageTtfText(
+ $image,
+ $size,
+ ( $rotation === null ? 0 : -$rotation->getRotation() ),
+ $position->x +
+ ( $rotation === null ? 0 : $rotation->get( 0, 2 ) ),
+ $position->y +
+ ( $rotation === null ? 0 : $rotation->get( 1, 2 ) ),
+ $this->allocate( $color ),
+ $path,
+ $text
+ );
+ break;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Writes text in a box of desired size
+ *
+ * @param string $string Text
+ * @param ezcGraphCoordinate $position Top left position
+ * @param float $width Width of text box
+ * @param float $height Height of text box
+ * @param int $align Alignement of text
+ * @param ezcGraphRotation $rotation
+ * @return void
+ */
+ public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null )
+ {
+ $padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 );
+
+ $width -= $padding * 2;
+ $height -= $padding * 2;
+ $position->x += $padding;
+ $position->y += $padding;
+
+ // Try to get a font size for the text to fit into the box
+ $maxSize = min( $height, $this->options->font->maxFontSize );
+ $result = false;
+ for ( $size = $maxSize; $size >= $this->options->font->minFontSize; --$size )
+ {
+ $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size );
+ if ( is_array( $result ) )
+ {
+ break;
+ }
+ $size = floor( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : $newsize );
+ }
+
+ if ( !is_array( $result ) )
+ {
+ if ( ( $height >= $this->options->font->minFontSize ) &&
+ ( $this->options->autoShortenString ) )
+ {
+ $result = $this->tryFitShortenedString( $string, $position, $width, $height, $size = $this->options->font->minFontSize );
+ }
+ else
+ {
+ throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height );
+ }
+ }
+
+ $this->options->font->minimalUsedFont = $size;
+
+ $this->strings[] = array(
+ 'text' => $result,
+ 'position' => $position,
+ 'width' => $width,
+ 'height' => $height,
+ 'align' => $align,
+ 'font' => $this->options->font,
+ 'rotation' => $rotation,
+ );
+
+ return array(
+ clone $position,
+ new ezcGraphCoordinate( $position->x + $width, $position->y ),
+ new ezcGraphCoordinate( $position->x + $width, $position->y + $height ),
+ new ezcGraphCoordinate( $position->x, $position->y + $height ),
+ );
+ }
+
+ /**
+ * Draw all collected texts
+ *
+ * The texts are collected and their maximum possible font size is
+ * calculated. This function finally draws the texts on the image, this
+ * delayed drawing has two reasons:
+ *
+ * 1) This way the text strings are always on top of the image, what
+ * results in better readable texts
+ * 2) The maximum possible font size can be calculated for a set of texts
+ * with the same font configuration. Strings belonging to one chart
+ * element normally have the same font configuration, so that all texts
+ * belonging to one element will have the same font size.
+ *
+ * @access protected
+ * @return void
+ */
+ protected function drawAllTexts()
+ {
+ $image = $this->getImage();
+
+ foreach ( $this->strings as $text )
+ {
+ $size = $text['font']->minimalUsedFont;
+
+ $completeHeight = count( $text['text'] ) * $size + ( count( $text['text'] ) - 1 ) * $this->options->lineSpacing;
+
+ // Calculate y offset for vertical alignement
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::BOTTOM ):
+ $yOffset = $text['height'] - $completeHeight;
+ break;
+ case ( $text['align'] & ezcGraph::MIDDLE ):
+ $yOffset = ( $text['height'] - $completeHeight ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::TOP ):
+ default:
+ $yOffset = 0;
+ break;
+ }
+
+ $padding = $text['font']->padding + $text['font']->borderWidth / 2;
+ if ( $this->options->font->minimizeBorder === true )
+ {
+ // Calculate maximum width of text rows
+ $width = false;
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ $boundings = $this->getTextBoundings( $size, $text['font'], $string );
+ if ( ( $width === false) || ( $boundings->width > $width ) )
+ {
+ $width = $boundings->width;
+ }
+ }
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::LEFT ):
+ $xOffset = 0;
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $xOffset = ( $text['width'] - $width ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $xOffset = $text['width'] - $width;
+ break;
+ }
+
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding + $xOffset,
+ $text['position']->y - $padding + $yOffset
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $xOffset + $width,
+ $text['position']->y - $padding + $yOffset
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $xOffset + $width,
+ $text['position']->y + $padding * 2 + $yOffset + $completeHeight
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding + $xOffset,
+ $text['position']->y + $padding * 2 + $yOffset + $completeHeight
+ ),
+ );
+ }
+ else
+ {
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding,
+ $text['position']->y - $padding
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $text['width'],
+ $text['position']->y - $padding
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $text['width'],
+ $text['position']->y + $padding * 2 + $text['height']
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding,
+ $text['position']->y + $padding * 2 + $text['height']
+ ),
+ );
+ }
+
+ if ( $text['rotation'] !== null )
+ {
+ foreach ( $borderPolygonArray as $nr => $point )
+ {
+ $borderPolygonArray[$nr] = $text['rotation']->transformCoordinate( $point );
+ }
+ }
+
+ if ( $text['font']->background !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->background,
+ true
+ );
+ }
+
+ if ( $text['font']->border !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->border,
+ false,
+ $text['font']->borderWidth
+ );
+ }
+
+ // Render text with evaluated font size
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ $boundings = $this->getTextBoundings( $size, $text['font'], $string );
+ $text['position']->y += $size;
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::LEFT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x,
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( $text['width'] - $boundings->width ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( ( $text['width'] - $boundings->width ) / 2 ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ }
+
+ // Calculate relative modification of rotation center point
+ if ( $text['rotation'] !== null )
+ {
+ $rotation = new ezcGraphRotation(
+ $text['rotation']->getRotation(),
+ new ezcGraphCoordinate(
+ $text['rotation']->getCenter()->x +
+ $position->x - $text['position']->x,
+ $text['rotation']->getCenter()->y +
+ $position->y - $text['position']->y
+ )
+ );
+ $rotation = $text['rotation'];
+ }
+ else
+ {
+ $rotation = null;
+ }
+
+ // Optionally draw text shadow
+ if ( $text['font']->textShadow === true )
+ {
+ $this->renderText(
+ $image,
+ $string,
+ $text['font']->type,
+ $text['font']->path,
+ $text['font']->textShadowColor,
+ new ezcGraphCoordinate(
+ $position->x + $text['font']->textShadowOffset,
+ $position->y + $text['font']->textShadowOffset
+ ),
+ $size,
+ $rotation
+ );
+ }
+
+ // Finally draw text
+ $this->renderText(
+ $image,
+ $string,
+ $text['font']->type,
+ $text['font']->path,
+ $text['font']->color,
+ $position,
+ $size,
+ $rotation
+ );
+
+ $text['position']->y += $size * $this->options->lineSpacing;
+ }
+ }
+ }
+
+ /**
+ * Draws a sector of cirlce
+ *
+ * @param ezcGraphCoordinate $center Center of circle
+ * @param mixed $width Width
+ * @param mixed $height Height
+ * @param mixed $startAngle Start angle of circle sector
+ * @param mixed $endAngle End angle of circle sector
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @return void
+ */
+ public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ $image = $this->getImage();
+ $drawColor = $this->allocate( $color );
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ if ( $filled )
+ {
+ imagefilledarc(
+ $image,
+ $this->supersample( $center->x ),
+ $this->supersample( $center->y ),
+ $this->supersample( $width ),
+ $this->supersample( $height ),
+ $startAngle,
+ $endAngle,
+ $drawColor,
+ IMG_ARC_PIE
+ );
+ }
+ else
+ {
+ imagefilledarc(
+ $image,
+ $this->supersample( $center->x ),
+ $this->supersample( $center->y ),
+ $this->supersample( $width ),
+ $this->supersample( $height ),
+ $startAngle,
+ $endAngle,
+ $drawColor,
+ IMG_ARC_PIE | IMG_ARC_NOFILL | IMG_ARC_EDGED
+ );
+ }
+
+ // Create polygon array to return
+ $polygonArray = array( $center );
+ for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
+ );
+ }
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
+ );
+
+ return $polygonArray;
+ }
+
+ /**
+ * Draws a single element of a circular arc
+ *
+ * ext/gd itself does not support something like circular arcs, so that
+ * this functions draws rectangular polygons as a part of circular arcs
+ * to interpolate them. This way it is possible to apply a linear gradient
+ * to the circular arc, because we draw single steps anyway.
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param integer $width Width of ellipse
+ * @param integer $height Height of ellipse
+ * @param integer $size Height of border
+ * @param float $startAngle Starting angle of circle sector
+ * @param float $endAngle Ending angle of circle sector
+ * @param ezcGraphColor $color Color of Border
+ * @return void
+ */
+ protected function drawCircularArcStep( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color )
+ {
+ $this->drawPolygon(
+ array(
+ new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 )
+ ),
+ new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 ) + $size
+ ),
+ new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 ) + $size
+ ),
+ new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
+ ),
+ ),
+ $color->darken( $this->options->shadeCircularArc * ( 1 + cos ( deg2rad( $startAngle ) ) ) / 2 ),
+ true
+ );
+ }
+
+ /**
+ * Draws a circular arc
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param integer $width Width of ellipse
+ * @param integer $height Height of ellipse
+ * @param integer $size Height of border
+ * @param float $startAngle Starting angle of circle sector
+ * @param float $endAngle Ending angle of circle sector
+ * @param ezcGraphColor $color Color of Border
+ * @param bool $filled
+ * @return void
+ */
+ public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ $image = $this->getImage();
+ $drawColor = $this->allocate( $color );
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ if ( $filled === true )
+ {
+ $startIteration = ceil( $startAngle / $this->options->detail ) * $this->options->detail;
+ $endIteration = floor( $endAngle / $this->options->detail ) * $this->options->detail;
+
+ if ( $startAngle < $startIteration )
+ {
+ // Draw initial step
+ $this->drawCircularArcStep(
+ $center,
+ $width,
+ $height,
+ $size,
+ $startAngle,
+ $startIteration,
+ $color
+ );
+ }
+
+ // Draw all steps
+ for ( ; $startIteration < $endIteration; $startIteration += $this->options->detail )
+ {
+ $this->drawCircularArcStep(
+ $center,
+ $width,
+ $height,
+ $size,
+ $startIteration,
+ $startIteration + $this->options->detail,
+ $color
+ );
+ }
+
+ if ( $endIteration < $endAngle )
+ {
+ // Draw closing step
+ $this->drawCircularArcStep(
+ $center,
+ $width,
+ $height,
+ $size,
+ $endIteration,
+ $endAngle,
+ $color
+ );
+ }
+ }
+ else
+ {
+ imagefilledarc(
+ $image,
+ $this->supersample( $center->x ),
+ $this->supersample( $center->y ),
+ $this->supersample( $width ),
+ $this->supersample( $height ),
+ $startAngle,
+ $endAngle,
+ $drawColor,
+ IMG_ARC_PIE | IMG_ARC_NOFILL
+ );
+ }
+
+ // Create polygon array to return
+ $polygonArray = array();
+ for ( $angle = $startAngle; $angle < $endAngle; $angle += $this->options->imageMapResolution )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
+ );
+ }
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $endAngle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $endAngle ) ) * $height ) / 2 )
+ );
+
+ for ( $angle = $endAngle; $angle > $startAngle; $angle -= $this->options->imageMapResolution )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ) + $size,
+ $center->y +
+ ( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
+ );
+ }
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $startAngle ) ) * $width ) / 2 ) + $size,
+ $center->y +
+ ( ( sin( deg2rad( $startAngle ) ) * $height ) / 2 )
+ );
+
+ return $polygonArray;
+ }
+
+ /**
+ * Draw circle
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param mixed $width Width of ellipse
+ * @param mixed $height height of ellipse
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @return void
+ */
+ public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true )
+ {
+ $image = $this->getImage();
+
+ $drawColor = $this->allocate( $color );
+
+ if ( $filled )
+ {
+ imagefilledellipse(
+ $image,
+ $this->supersample( $center->x ),
+ $this->supersample( $center->y ),
+ $this->supersample( $width ),
+ $this->supersample( $height ),
+ $drawColor
+ );
+ }
+ else
+ {
+ imageellipse(
+ $image,
+ $this->supersample( $center->x ),
+ $this->supersample( $center->y ),
+ $this->supersample( $width ),
+ $this->supersample( $height ),
+ $drawColor
+ );
+ }
+
+ $polygonArray = array();
+ for ( $angle = 0; $angle < 360; $angle += $this->options->imageMapResolution )
+ {
+ $polygonArray[] = new ezcGraphCoordinate(
+ $center->x +
+ ( ( cos( deg2rad( $angle ) ) * $width ) / 2 ),
+ $center->y +
+ ( ( sin( deg2rad( $angle ) ) * $height ) / 2 )
+ );
+ }
+
+ return $polygonArray;
+ }
+
+ /**
+ * Draw an image
+ *
+ * The actual drawing of the image is delayed, to not apply supersampling
+ * to the image. The image will normally be resized using the gd function
+ * imagecopyresampled, which provides nice antialiased scaling, so that
+ * additional supersampling would make the image look blurred. The delayed
+ * images will be pre-processed, so that they are draw in the back of
+ * everything else.
+ *
+ * @param mixed $file Image file
+ * @param ezcGraphCoordinate $position Top left position
+ * @param mixed $width Width of image in destination image
+ * @param mixed $height Height of image in destination image
+ * @return void
+ */
+ public function drawImage( $file, ezcGraphCoordinate $position, $width, $height )
+ {
+ $this->preProcessImages[] = array(
+ 'file' => $file,
+ 'position' => clone $position,
+ 'width' => $width,
+ 'height' => $height,
+ );
+
+ return array(
+ $position,
+ new ezcGraphCoordinate( $position->x + $width, $position->y ),
+ new ezcGraphCoordinate( $position->x + $width, $position->y + $height ),
+ new ezcGraphCoordinate( $position->x, $position->y + $height ),
+ );
+ }
+
+ /**
+ * Draw all images to image resource handler
+ *
+ * @param resource $image Image to draw on
+ * @return resource Updated image resource
+ */
+ protected function addImages( $image )
+ {
+ foreach ( $this->preProcessImages as $preImage )
+ {
+ $preImageData = $this->imageCreateFrom( $preImage['file'] );
+ call_user_func_array(
+ $this->options->resampleFunction,
+ array(
+ $image,
+ $preImageData['image'],
+ $preImage['position']->x, $preImage['position']->y,
+ 0, 0,
+ $preImage['width'], $preImage['height'],
+ $preImageData['width'], $preImageData['height'],
+ )
+ );
+ }
+
+ return $image;
+ }
+
+ /**
+ * Return mime type for current image format
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ switch ( $this->options->imageFormat )
+ {
+ case IMG_PNG:
+ return 'image/png';
+ case IMG_JPEG:
+ return 'image/jpeg';
+ }
+ }
+
+ /**
+ * Render image directly to output
+ *
+ * The method renders the image directly to the standard output. You
+ * normally do not want to use this function, because it makes it harder
+ * to proper cache the generated graphs.
+ *
+ * @return void
+ */
+ public function renderToOutput()
+ {
+ header( 'Content-Type: ' . $this->getMimeType() );
+ $this->render( null );
+ }
+
+ /**
+ * Finally save image
+ *
+ * @param string $file Destination filename
+ * @return void
+ */
+ public function render( $file )
+ {
+ $destination = imagecreatetruecolor( $this->options->width, $this->options->height );
+
+ // Default to a transparent white background
+ $bgColor = imagecolorallocatealpha( $destination, 255, 255, 255, 127 );
+ imagealphablending( $destination, true );
+ imagesavealpha( $destination, true );
+ imagefill( $destination, 1, 1, $bgColor );
+
+ // Apply background if one is defined
+ if ( $this->options->background !== false )
+ {
+ $background = $this->imageCreateFrom( $this->options->background );
+
+ call_user_func_array(
+ $this->options->resampleFunction,
+ array(
+ $destination,
+ $background['image'],
+ 0, 0,
+ 0, 0,
+ $this->options->width, $this->options->height,
+ $background['width'], $background['height'],
+ )
+ );
+ }
+
+ // Draw all images to exclude them from supersampling
+ $destination = $this->addImages( $destination );
+
+ // Finally merge with graph
+ $image = $this->getImage();
+ call_user_func_array(
+ $this->options->resampleFunction,
+ array(
+ $destination,
+ $image,
+ 0, 0,
+ 0, 0,
+ $this->options->width, $this->options->height,
+ $this->supersample( $this->options->width ), $this->supersample( $this->options->height )
+ )
+ );
+
+ $this->image = $destination;
+ imagedestroy( $image );
+
+ // Draw all texts
+ // Reset supersampling during text rendering
+ $supersampling = $this->options->supersampling;
+ $this->options->supersampling = 1;
+ $this->drawAllTexts();
+ $this->options->supersampling = $supersampling;
+
+ $image = $this->getImage();
+ switch ( $this->options->imageFormat )
+ {
+ case IMG_PNG:
+ if ( $file === null )
+ {
+ imagepng( $image );
+ }
+ else
+ {
+ imagepng( $image, $file );
+ }
+ break;
+ case IMG_JPEG:
+ imagejpeg( $image, $file, $this->options->jpegQuality );
+ break;
+ default:
+ throw new ezcGraphGdDriverUnsupportedImageTypeException( $this->options->imageFormat );
+ }
+ }
+
+ /**
+ * Get resource of rendered result
+ *
+ * Return the resource of the rendered result. You should not use this
+ * method before you called either renderToOutput() or render(), as the
+ * image may not be completely rendered until then.
+ *
+ * @return resource
+ */
+ public function getResource()
+ {
+ return $this->image;
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/driver/svg.php b/include/ezcomponents/Graph/src/driver/svg.php
new file mode 100644
index 000000000..687d657e6
--- /dev/null
+++ b/include/ezcomponents/Graph/src/driver/svg.php
@@ -0,0 +1,1230 @@
+
+ * $graph = new ezcGraphPieChart();
+ * $graph->background->color = '#FFFFFFFF';
+ * $graph->title = 'Access statistics';
+ * $graph->legend = false;
+ *
+ * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array(
+ * 'Mozilla' => 19113,
+ * 'Explorer' => 10917,
+ * 'Opera' => 1464,
+ * 'Safari' => 652,
+ * 'Konqueror' => 474,
+ * ) );
+ *
+ * $graph->renderer = new ezcGraphRenderer3d();
+ * $graph->renderer->options->pieChartShadowSize = 10;
+ * $graph->renderer->options->pieChartGleam = .5;
+ * $graph->renderer->options->dataBorder = false;
+ * $graph->renderer->options->pieChartHeight = 16;
+ * $graph->renderer->options->legendSymbolGleam = .5;
+ *
+ * // SVG driver options
+ * $graph->driver->options->templateDocument = dirname( __FILE__ ) . '/template.svg';
+ * $graph->driver->options->graphOffset = new ezcGraphCoordinate( 25, 40 );
+ * $graph->driver->options->insertIntoGroup = 'ezcGraph';
+ *
+ * $graph->render( 400, 200, 'tutorial_driver_svg.svg' );
+ *
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphSvgDriver extends ezcGraphDriver
+{
+
+ /**
+ * DOM tree of the svg document
+ *
+ * @var DOMDocument
+ */
+ protected $dom;
+
+ /**
+ * DOMElement containing all svg style definitions
+ *
+ * @var DOMElement
+ */
+ protected $defs;
+
+ /**
+ * DOMElement containing all svg objects
+ *
+ * @var DOMElement
+ */
+ protected $elements;
+
+ /**
+ * List of strings to draw
+ * array ( array(
+ * 'text' => array( 'strings' ),
+ * 'options' => ezcGraphFontOptions,
+ * )
+ *
+ * @var array
+ */
+ protected $strings = array();
+
+ /**
+ * List of already created gradients
+ *
+ * @var array
+ */
+ protected $drawnGradients = array();
+
+ /**
+ * Numeric unique element id
+ *
+ * @var int
+ */
+ protected $elementID = 0;
+
+ /**
+ * Font storage for SVG font glyphs and kernings.
+ *
+ * @var ezcGraphSvgFont
+ */
+ protected $font = null;
+
+ /**
+ * Constructor
+ *
+ * @param array $options Default option array
+ * @return void
+ * @ignore
+ */
+ public function __construct( array $options = array() )
+ {
+ ezcBase::checkDependency( 'Graph', ezcBase::DEP_PHP_EXTENSION, 'dom' );
+ $this->options = new ezcGraphSvgDriverOptions( $options );
+ $this->font = new ezcGraphSvgFont();
+ }
+
+ /**
+ * Creates the DOM object to insert SVG nodes in.
+ *
+ * If the DOM document does not exists it will be created or loaded
+ * according to the settings.
+ *
+ * @return void
+ */
+ protected function createDocument()
+ {
+ if ( $this->dom === null )
+ {
+ // Create encoding based dom document
+ if ( $this->options->encoding !== null )
+ {
+ $this->dom = new DOMDocument( '1.0', $this->options->encoding );
+ }
+ else
+ {
+ $this->dom = new DOMDocument( '1.0' );
+ }
+
+ if ( $this->options->templateDocument !== false )
+ {
+ $this->dom->load( $this->options->templateDocument );
+
+ $this->defs = $this->dom->getElementsByTagName( 'defs' )->item( 0 );
+ $svg = $this->dom->getElementsByTagName( 'svg' )->item( 0 );
+ }
+ else
+ {
+ $svg = $this->dom->createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
+ $this->dom->appendChild( $svg );
+
+ $svg->setAttribute( 'width', $this->options->width );
+ $svg->setAttribute( 'height', $this->options->height );
+ $svg->setAttribute( 'version', '1.0' );
+ $svg->setAttribute( 'id', $this->options->idPrefix );
+
+ $this->defs = $this->dom->createElement( 'defs' );
+ $this->defs = $svg->appendChild( $this->defs );
+ }
+
+ if ( $this->options->insertIntoGroup !== false )
+ {
+ // getElementById only works for Documents validated against a certain
+ // schema, so that the use of XPath should be faster in most cases.
+ $xpath = new DomXPath( $this->dom );
+ $this->elements = $xpath->query( '//*[@id = \'' . $this->options->insertIntoGroup . '\']' )->item( 0 );
+ if ( !$this->elements )
+ {
+ throw new ezcGraphSvgDriverInvalidIdException( $this->options->insertIntoGroup );
+ }
+ }
+ else
+ {
+ $this->elements = $this->dom->createElement( 'g' );
+ $this->elements->setAttribute( 'id', $this->options->idPrefix . 'Chart' );
+ $this->elements->setAttribute( 'color-rendering', $this->options->colorRendering );
+ $this->elements->setAttribute( 'shape-rendering', $this->options->shapeRendering );
+ $this->elements->setAttribute( 'text-rendering', $this->options->textRendering );
+ $this->elements = $svg->appendChild( $this->elements );
+ }
+ }
+ }
+
+ /**
+ * Return gradient URL
+ *
+ * Creates the definitions needed for a gradient, if a proper gradient does
+ * not yet exists. In each case a URL referencing the correct gradient will
+ * be returned.
+ *
+ * @param ezcGraphColor $color Gradient
+ * @return string Gradient URL
+ */
+ protected function getGradientUrl( ezcGraphColor $color )
+ {
+ switch ( true )
+ {
+ case ( $color instanceof ezcGraphLinearGradient ):
+ if ( !in_array( $color->__toString(), $this->drawnGradients, true ) )
+ {
+ $gradient = $this->dom->createElement( 'linearGradient' );
+ $gradient->setAttribute( 'id', 'Definition_' . $color->__toString() );
+ $this->defs->appendChild( $gradient );
+
+ // Start of linear gradient
+ $stop = $this->dom->createElement( 'stop' );
+ $stop->setAttribute( 'offset', 0 );
+ $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;',
+ $color->startColor->red,
+ $color->startColor->green,
+ $color->startColor->blue,
+ 1 - ( $color->startColor->alpha / 255 )
+ )
+ );
+ $gradient->appendChild( $stop );
+
+ // End of linear gradient
+ $stop = $this->dom->createElement( 'stop' );
+ $stop->setAttribute( 'offset', 1 );
+ $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;',
+ $color->endColor->red,
+ $color->endColor->green,
+ $color->endColor->blue,
+ 1 - ( $color->endColor->alpha / 255 )
+ )
+ );
+ $gradient->appendChild( $stop );
+
+ $gradient = $this->dom->createElement( 'linearGradient' );
+ $gradient->setAttribute( 'id', $color->__toString() );
+ $gradient->setAttribute( 'x1', sprintf( '%.4F', $color->startPoint->x ) );
+ $gradient->setAttribute( 'y1', sprintf( '%.4F', $color->startPoint->y ) );
+ $gradient->setAttribute( 'x2', sprintf( '%.4F', $color->endPoint->x ) );
+ $gradient->setAttribute( 'y2', sprintf( '%.4F', $color->endPoint->y ) );
+ $gradient->setAttribute( 'gradientUnits', 'userSpaceOnUse' );
+ $gradient->setAttributeNS(
+ 'http://www.w3.org/1999/xlink',
+ 'xlink:href',
+ '#Definition_' . $color->__toString()
+ );
+ $this->defs->appendChild( $gradient );
+
+ $this->drawnGradients[] = $color->__toString();
+ }
+
+ return sprintf( 'url(#%s)',
+ $color->__toString()
+ );
+ case ( $color instanceof ezcGraphRadialGradient ):
+ if ( !in_array( $color->__toString(), $this->drawnGradients, true ) )
+ {
+ $gradient = $this->dom->createElement( 'linearGradient' );
+ $gradient->setAttribute( 'id', 'Definition_' . $color->__toString() );
+ $this->defs->appendChild( $gradient );
+
+ // Start of linear gradient
+ $stop = $this->dom->createElement( 'stop' );
+ $stop->setAttribute( 'offset', 0 );
+ $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;',
+ $color->startColor->red,
+ $color->startColor->green,
+ $color->startColor->blue,
+ 1 - ( $color->startColor->alpha / 255 )
+ )
+ );
+ $gradient->appendChild( $stop );
+
+ // End of linear gradient
+ $stop = $this->dom->createElement( 'stop' );
+ $stop->setAttribute( 'offset', 1 );
+ $stop->setAttribute( 'style', sprintf( 'stop-color: #%02x%02x%02x; stop-opacity: %.2F;',
+ $color->endColor->red,
+ $color->endColor->green,
+ $color->endColor->blue,
+ 1 - ( $color->endColor->alpha / 255 )
+ )
+ );
+ $gradient->appendChild( $stop );
+
+ $gradient = $this->dom->createElement( 'radialGradient' );
+ $gradient->setAttribute( 'id', $color->__toString() );
+ $gradient->setAttribute( 'cx', sprintf( '%.4F', $color->center->x ) );
+ $gradient->setAttribute( 'cy', sprintf( '%.4F', $color->center->y ) );
+ $gradient->setAttribute( 'fx', sprintf( '%.4F', $color->center->x ) );
+ $gradient->setAttribute( 'fy', sprintf( '%.4F', $color->center->y ) );
+ $gradient->setAttribute( 'r', max( $color->height, $color->width ) );
+ $gradient->setAttribute( 'gradientUnits', 'userSpaceOnUse' );
+ $gradient->setAttributeNS(
+ 'http://www.w3.org/1999/xlink',
+ 'xlink:href',
+ '#Definition_' . $color->__toString()
+ );
+ $this->defs->appendChild( $gradient );
+
+ $this->drawnGradients[] = $color->__toString();
+ }
+
+ return sprintf( 'url(#%s)',
+ $color->__toString()
+ );
+ default:
+ return false;
+ }
+
+ }
+
+ /**
+ * Get SVG style definition
+ *
+ * Returns a string with SVG style definitions created from color,
+ * fillstatus and line thickness.
+ *
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @param float $thickness Line thickness.
+ * @return string Formatstring
+ */
+ protected function getStyle( ezcGraphColor $color, $filled = true, $thickness = 1. )
+ {
+ if ( $filled )
+ {
+ if ( $url = $this->getGradientUrl( $color ) )
+ {
+ return sprintf( 'fill: %s; stroke: none;', $url );
+ }
+ else
+ {
+ return sprintf( 'fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ 1 - ( $color->alpha / 255 )
+ );
+ }
+ }
+ else
+ {
+ if ( $url = $this->getGradientUrl( $color ) )
+ {
+ return sprintf( 'fill: none; stroke: %s;', $url );
+ }
+ else
+ {
+ return sprintf( 'fill: none; stroke: #%02x%02x%02x; stroke-width: %d; stroke-opacity: %.2F; stroke-linecap: %s; stroke-linejoin: %s;',
+ $color->red,
+ $color->green,
+ $color->blue,
+ $thickness,
+ 1 - ( $color->alpha / 255 ),
+ $this->options->strokeLineCap,
+ $this->options->strokeLineJoin
+ );
+ }
+ }
+ }
+
+ /**
+ * Draws a single polygon.
+ *
+ * @param array $points Point array
+ * @param ezcGraphColor $color Polygon color
+ * @param mixed $filled Filled
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. )
+ {
+ $this->createDocument();
+
+ if ( !$filled )
+ {
+ // The middle of the border is on the outline of a polygon in SVG,
+ // fix that:
+ try
+ {
+ $points = $this->reducePolygonSize( $points, $thickness / 2 );
+ }
+ catch ( ezcGraphReducementFailedException $e )
+ {
+ return false;
+ }
+ }
+
+ $lastPoint = end( $points );
+ $pointString = sprintf( ' M %.4F,%.4F',
+ $lastPoint->x + $this->options->graphOffset->x,
+ $lastPoint->y + $this->options->graphOffset->y
+ );
+
+ foreach ( $points as $point )
+ {
+ $pointString .= sprintf( ' L %.4F,%.4F',
+ $point->x + $this->options->graphOffset->x,
+ $point->y + $this->options->graphOffset->y
+ );
+ }
+ $pointString .= ' z ';
+
+ $path = $this->dom->createElement( 'path' );
+ $path->setAttribute( 'd', $pointString );
+
+ $path->setAttribute(
+ 'style',
+ $this->getStyle( $color, $filled, $thickness )
+ );
+ $path->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Polygon_' . ++$this->elementID ) );
+ $this->elements->appendChild( $path );
+
+ return $id;
+ }
+
+ /**
+ * Draws a line
+ *
+ * @param ezcGraphCoordinate $start Start point
+ * @param ezcGraphCoordinate $end End point
+ * @param ezcGraphColor $color Line color
+ * @param float $thickness Line thickness
+ * @return void
+ */
+ public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. )
+ {
+ $this->createDocument();
+
+ $pointString = sprintf( ' M %.4F,%.4F L %.4F,%.4F',
+ $start->x + $this->options->graphOffset->x,
+ $start->y + $this->options->graphOffset->y,
+ $end->x + $this->options->graphOffset->x,
+ $end->y + $this->options->graphOffset->y
+ );
+
+ $path = $this->dom->createElement( 'path' );
+ $path->setAttribute( 'd', $pointString );
+ $path->setAttribute(
+ 'style',
+ $this->getStyle( $color, false, $thickness )
+ );
+
+ $path->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Line_' . ++$this->elementID ) );
+ $this->elements->appendChild( $path );
+
+ return $id;
+ }
+
+ /**
+ * Returns boundings of text depending on the available font extension
+ *
+ * @param float $size Textsize
+ * @param ezcGraphFontOptions $font Font
+ * @param string $text Text
+ * @return ezcGraphBoundings Boundings of text
+ */
+ protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text )
+ {
+ if ( $font->type === ezcGraph::SVG_FONT )
+ {
+ return new ezcGraphBoundings(
+ 0,
+ 0,
+ $this->font->calculateStringWidth( $font->path, $text ) * $size,
+ $size
+ );
+ }
+ else
+ {
+ // If we didn't get a SVG font, continue guessing the font width.
+ return new ezcGraphBoundings(
+ 0,
+ 0,
+ $this->getTextWidth( $text, $size ),
+ $size
+ );
+ }
+ }
+
+ /**
+ * Writes text in a box of desired size
+ *
+ * @param string $string Text
+ * @param ezcGraphCoordinate $position Top left position
+ * @param float $width Width of text box
+ * @param float $height Height of text box
+ * @param int $align Alignement of text
+ * @param ezcGraphRotation $rotation
+ * @return void
+ */
+ public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null )
+ {
+ $padding = $this->options->font->padding + ( $this->options->font->border !== false ? $this->options->font->borderWidth : 0 );
+
+ $width -= $padding * 2;
+ $height -= $padding * 2;
+ $textPosition = new ezcGraphCoordinate(
+ $position->x + $padding,
+ $position->y + $padding
+ );
+
+ // Try to get a font size for the text to fit into the box
+ $maxSize = min( $height, $this->options->font->maxFontSize );
+ $result = false;
+ for ( $size = $maxSize; $size >= $this->options->font->minFontSize; )
+ {
+ $result = $this->testFitStringInTextBox( $string, $position, $width, $height, $size );
+ if ( is_array( $result ) )
+ {
+ break;
+ }
+ $size = ( ( $newsize = $size * ( $result ) ) >= $size ? $size - 1 : floor( $newsize ) );
+ }
+
+ if ( !is_array( $result ) )
+ {
+ if ( ( $height >= $this->options->font->minFontSize ) &&
+ ( $this->options->autoShortenString ) )
+ {
+ $result = $this->tryFitShortenedString( $string, $position, $width, $height, $size = $this->options->font->minFontSize );
+ }
+ else
+ {
+ throw new ezcGraphFontRenderingException( $string, $this->options->font->minFontSize, $width, $height );
+ }
+ }
+
+ $this->options->font->minimalUsedFont = $size;
+ $this->strings[] = array(
+ 'text' => $result,
+ 'id' => $id = ( $this->options->idPrefix . 'TextBox_' . ++$this->elementID ),
+ 'position' => $textPosition,
+ 'width' => $width,
+ 'height' => $height,
+ 'align' => $align,
+ 'font' => $this->options->font,
+ 'rotation' => $rotation,
+ );
+
+ return $id;
+ }
+
+ /**
+ * Guess text width for string
+ *
+ * The is no way to know the font or fontsize used by the SVG renderer to
+ * render the string. We assume some character width defined in the SVG
+ * driver options, tu guess the length of a string. We discern between
+ * numeric an non numeric strings, because we often use only numeric
+ * strings to display chart data and numbers tend to be a bit wider then
+ * characters.
+ *
+ * @param mixed $string
+ * @param mixed $size
+ * @access protected
+ * @return void
+ */
+ protected function getTextWidth( $string, $size )
+ {
+ switch ( strtolower( $this->options->encoding ) )
+ {
+ case '':
+ case 'utf-8':
+ case 'utf-16':
+ $string = utf8_decode( $string );
+ break;
+ }
+
+ if ( is_numeric( $string ) )
+ {
+ return $size * strlen( $string ) * $this->options->assumedNumericCharacterWidth;
+ }
+ else
+ {
+ return $size * strlen( $string ) * $this->options->assumedTextCharacterWidth;
+ }
+ }
+
+ /**
+ * Encodes non-utf-8 strings
+ *
+ * Transforms non-utf-8 strings to their hex entities, because ext/DOM
+ * fails here with conversion errors.
+ *
+ * @param string $string
+ * @return string
+ */
+ protected function encode( $string )
+ {
+ $string = htmlspecialchars( $string );
+
+ switch ( strtolower( $this->options->encoding ) )
+ {
+ case '':
+ case 'utf-8':
+ case 'utf-16':
+ return $string;
+ default:
+ // Manual escaping of non ANSII characters, because ext/DOM fails here
+ return preg_replace_callback(
+ '/[\\x80-\\xFF]/',
+ create_function(
+ '$char',
+ 'return sprintf( \'%02x;\', ord( $char[0] ) );'
+ ),
+ $string
+ );
+ }
+ }
+
+ /**
+ * Draw all collected texts
+ *
+ * The texts are collected and their maximum possible font size is
+ * calculated. This function finally draws the texts on the image, this
+ * delayed drawing has two reasons:
+ *
+ * 1) This way the text strings are always on top of the image, what
+ * results in better readable texts
+ * 2) The maximum possible font size can be calculated for a set of texts
+ * with the same font configuration. Strings belonging to one chart
+ * element normally have the same font configuration, so that all texts
+ * belonging to one element will have the same font size.
+ *
+ * @access protected
+ * @return void
+ */
+ protected function drawAllTexts()
+ {
+ $elementsRoot = $this->elements;
+
+ foreach ( $this->strings as $text )
+ {
+ // Add all text elements into one group
+ $group = $this->dom->createElement( 'g' );
+ $group->setAttribute( 'id', $text['id'] );
+
+ if ( $text['rotation'] !== null )
+ {
+ $group->setAttribute( 'transform', sprintf( 'rotate( %.2F %.4F %.4F )',
+ $text['rotation']->getRotation(),
+ $text['rotation']->getCenter()->x,
+ $text['rotation']->getCenter()->y
+ ) );
+ }
+
+ $group = $elementsRoot->appendChild( $group );
+
+ $size = $text['font']->minimalUsedFont;
+ $font = $text['font']->name;
+
+ $completeHeight = count( $text['text'] ) * $size + ( count( $text['text'] ) - 1 ) * $this->options->lineSpacing;
+
+ // Calculate y offset for vertical alignement
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::BOTTOM ):
+ $yOffset = $text['height'] - $completeHeight;
+ break;
+ case ( $text['align'] & ezcGraph::MIDDLE ):
+ $yOffset = ( $text['height'] - $completeHeight ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::TOP ):
+ default:
+ $yOffset = 0;
+ break;
+ }
+
+ $padding = $text['font']->padding + $text['font']->borderWidth / 2;
+ if ( $this->options->font->minimizeBorder === true )
+ {
+ // Calculate maximum width of text rows
+ $width = false;
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+ if ( ( $strWidth = $this->getTextBoundings( $size, $text['font'], $string )->width ) > $width )
+ {
+ $width = $strWidth;
+ }
+ }
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::LEFT ):
+ $xOffset = 0;
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $xOffset = ( $text['width'] - $width ) / 2;
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $xOffset = $text['width'] - $width;
+ break;
+ }
+
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding + $xOffset,
+ $text['position']->y - $padding + $yOffset
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $xOffset + $width,
+ $text['position']->y - $padding + $yOffset
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $xOffset + $width,
+ $text['position']->y + $padding * 2 + $yOffset + $completeHeight
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding + $xOffset,
+ $text['position']->y + $padding * 2 + $yOffset + $completeHeight
+ ),
+ );
+ }
+ else
+ {
+ $borderPolygonArray = array(
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding,
+ $text['position']->y - $padding
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $text['width'],
+ $text['position']->y - $padding
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x + $padding * 2 + $text['width'],
+ $text['position']->y + $padding * 2 + $text['height']
+ ),
+ new ezcGraphCoordinate(
+ $text['position']->x - $padding,
+ $text['position']->y + $padding * 2 + $text['height']
+ ),
+ );
+ }
+
+ // Set elements root temporary to local text group to ensure
+ // background and border beeing elements of text group
+ $this->elements = $group;
+ if ( $text['font']->background !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->background,
+ true
+ );
+ }
+ else
+ {
+ // Always draw full tranparent background polygon as fallback,
+ // to be able to click on complete font space, not only on
+ // the text
+ $this->drawPolygon(
+ $borderPolygonArray,
+ ezcGraphColor::fromHex( '#FFFFFFFF' ),
+ true
+ );
+ }
+
+ if ( $text['font']->border !== false )
+ {
+ $this->drawPolygon(
+ $borderPolygonArray,
+ $text['font']->border,
+ false,
+ $text['font']->borderWidth
+ );
+ }
+ $this->elements = $elementsRoot;
+
+ // Bottom line for SVG fonts is lifted a bit
+ $text['position']->y += $size * .85;
+
+ // Render text with evaluated font size
+ foreach ( $text['text'] as $line )
+ {
+ $string = implode( ' ', $line );
+
+ switch ( true )
+ {
+ case ( $text['align'] & ezcGraph::LEFT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x,
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::RIGHT ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( $text['width'] - $this->getTextBoundings( $size, $text['font'], $string )->width ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ case ( $text['align'] & ezcGraph::CENTER ):
+ $position = new ezcGraphCoordinate(
+ $text['position']->x + ( ( $text['width'] - $this->getTextBoundings( $size, $text['font'], $string )->width ) / 2 ),
+ $text['position']->y + $yOffset
+ );
+ break;
+ }
+
+ // Optionally draw text shadow
+ if ( $text['font']->textShadow === true )
+ {
+ $textNode = $this->dom->createElement( 'text', $this->encode( $string ) );
+ $textNode->setAttribute( 'id', $text['id'] . '_shadow' );
+ $textNode->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x + $text['font']->textShadowOffset ) );
+ $textNode->setAttribute( 'text-length', sprintf( '%.4Fpx', $this->getTextBoundings( $size, $text['font'], $string )->width ) );
+ $textNode->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y + $text['font']->textShadowOffset ) );
+ $textNode->setAttribute(
+ 'style',
+ sprintf(
+ 'font-size: %dpx; font-family: \'%s\'; fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;',
+ $size,
+ $text['font']->name,
+ $text['font']->textShadowColor->red,
+ $text['font']->textShadowColor->green,
+ $text['font']->textShadowColor->blue,
+ 1 - ( $text['font']->textShadowColor->alpha / 255 )
+ )
+ );
+ $group->appendChild( $textNode );
+ }
+
+ // Finally draw text
+ $textNode = $this->dom->createElement( 'text', $this->encode( $string ) );
+ $textNode->setAttribute( 'id', $text['id'] . '_text' );
+ $textNode->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x ) );
+ $textNode->setAttribute( 'text-length', sprintf( '%.4Fpx', $this->getTextBoundings( $size, $text['font'], $string )->width ) );
+ $textNode->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y ) );
+ $textNode->setAttribute(
+ 'style',
+ sprintf(
+ 'font-size: %dpx; font-family: \'%s\'; fill: #%02x%02x%02x; fill-opacity: %.2F; stroke: none;',
+ $size,
+ $text['font']->name,
+ $text['font']->color->red,
+ $text['font']->color->green,
+ $text['font']->color->blue,
+ 1 - ( $text['font']->color->alpha / 255 )
+ )
+ );
+ $group->appendChild( $textNode );
+
+ $text['position']->y += $size + $size * $this->options->lineSpacing;
+ }
+ }
+ }
+
+ /**
+ * Draws a sector of cirlce
+ *
+ * @param ezcGraphCoordinate $center Center of circle
+ * @param mixed $width Width
+ * @param mixed $height Height
+ * @param mixed $startAngle Start angle of circle sector
+ * @param mixed $endAngle End angle of circle sector
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled;
+ * @return void
+ */
+ public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ $this->createDocument();
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ if ( ( $endAngle - $startAngle ) >= 360 )
+ {
+ return $this->drawCircle( $center, $width, $height, $color, $filled );
+ }
+
+ // We need the radius
+ $width /= 2;
+ $height /= 2;
+
+ // Apply offset to copy of center coordinate
+ $center = clone $center;
+ $center->x += $this->options->graphOffset->x;
+ $center->y += $this->options->graphOffset->y;
+
+ if ( $filled )
+ {
+ $Xstart = $center->x + $width * cos( -deg2rad( $startAngle ) );
+ $Ystart = $center->y + $height * sin( deg2rad( $startAngle ) );
+ $Xend = $center->x + $width * cos( ( -deg2rad( $endAngle ) ) );
+ $Yend = $center->y + $height * sin( ( deg2rad( $endAngle ) ) );
+
+ $arc = $this->dom->createElement( 'path' );
+ $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F L %.2F,%.2F A %.2F,%.2F 0 %d,1 %.2F,%.2F z',
+ // Middle
+ $center->x, $center->y,
+ // Startpoint
+ $Xstart, $Ystart,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Endpoint
+ $Xend, $Yend
+ )
+ );
+
+ $arc->setAttribute(
+ 'style',
+ $this->getStyle( $color, $filled, 1 )
+ );
+ $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircleSector_' . ++$this->elementID ) );
+ $this->elements->appendChild( $arc );
+ return $id;
+ }
+ else
+ {
+ try
+ {
+ $reduced = $this->reduceEllipseSize( $center, $width * 2, $height * 2, $startAngle, $endAngle, .5 );
+ }
+ catch ( ezcGraphReducementFailedException $e )
+ {
+ return false;
+ }
+
+ $arc = $this->dom->createElement( 'path' );
+ $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F L %.2F,%.2F A %.2F,%.2F 0 %d,1 %.2F,%.2F z',
+ // Middle
+ $reduced['center']->x, $reduced['center']->y,
+ // Startpoint
+ $reduced['start']->x, $reduced['start']->y,
+ // Radius
+ $width - .5, $height - .5,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Endpoint
+ $reduced['end']->x, $reduced['end']->y
+ )
+ );
+
+ $arc->setAttribute(
+ 'style',
+ $this->getStyle( $color, $filled, 1 )
+ );
+
+ $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircleSector_' . ++$this->elementID ) );
+ $this->elements->appendChild( $arc );
+
+ return $id;
+ }
+ }
+
+ /**
+ * Draws a circular arc
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param integer $width Width of ellipse
+ * @param integer $height Height of ellipse
+ * @param integer $size Height of border
+ * @param float $startAngle Starting angle of circle sector
+ * @param float $endAngle Ending angle of circle sector
+ * @param ezcGraphColor $color Color of Border
+ * @param bool $filled
+ * @return void
+ */
+ public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true )
+ {
+ $this->createDocument();
+
+ // Normalize angles
+ if ( $startAngle > $endAngle )
+ {
+ $tmp = $startAngle;
+ $startAngle = $endAngle;
+ $endAngle = $tmp;
+ }
+
+ if ( ( $endAngle - $startAngle > 180 ) ||
+ ( ( $startAngle % 180 != 0) && ( $endAngle % 180 != 0) && ( ( $startAngle % 360 > 180 ) XOR ( $endAngle % 360 > 180 ) ) ) )
+ {
+ // Border crosses he 180 degrees border
+ $intersection = floor( $endAngle / 180 ) * 180;
+ while ( $intersection >= $endAngle )
+ {
+ $intersection -= 180;
+ }
+
+ $this->drawCircularArc( $center, $width, $height, $size, $startAngle, $intersection, $color, $filled );
+ $this->drawCircularArc( $center, $width, $height, $size, $intersection, $endAngle, $color, $filled );
+ return;
+ }
+
+ // We need the radius
+ $width /= 2;
+ $height /= 2;
+
+ $Xstart = $center->x + $this->options->graphOffset->x + $width * cos( -deg2rad( $startAngle ) );
+ $Ystart = $center->y + $this->options->graphOffset->y + $height * sin( deg2rad( $startAngle ) );
+ $Xend = $center->x + $this->options->graphOffset->x + $width * cos( ( -deg2rad( $endAngle ) ) );
+ $Yend = $center->y + $this->options->graphOffset->y + $height * sin( ( deg2rad( $endAngle ) ) );
+
+ if ( $filled === true )
+ {
+ $arc = $this->dom->createElement( 'path' );
+ $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F A %.2F,%.2F 0 %d,0 %.2F,%.2F L %.2F,%.2F A %.2F,%2F 0 %d,1 %.2F,%.2F z',
+ // Endpoint low
+ $Xend, $Yend + $size,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Startpoint low
+ $Xstart, $Ystart + $size,
+ // Startpoint
+ $Xstart, $Ystart,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Endpoint
+ $Xend, $Yend
+ )
+ );
+ }
+ else
+ {
+ $arc = $this->dom->createElement( 'path' );
+ $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F A %.2F,%.2F 0 %d,1 %.2F,%.2F',
+ // Startpoint
+ $Xstart, $Ystart,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Endpoint
+ $Xend, $Yend
+ )
+ );
+ }
+
+ $arc->setAttribute(
+ 'style',
+ $this->getStyle( $color, $filled )
+ );
+
+ $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircularArc_' . ++$this->elementID ) );
+ $this->elements->appendChild( $arc );
+
+ if ( ( $this->options->shadeCircularArc !== false ) &&
+ $filled )
+ {
+ $gradient = new ezcGraphLinearGradient(
+ new ezcGraphCoordinate(
+ $center->x - $width,
+ $center->y
+ ),
+ new ezcGraphCoordinate(
+ $center->x + $width,
+ $center->y
+ ),
+ ezcGraphColor::fromHex( '#FFFFFF' )->transparent( $this->options->shadeCircularArc * 1.5 ),
+ ezcGraphColor::fromHex( '#000000' )->transparent( $this->options->shadeCircularArc )
+ );
+
+ $arc = $this->dom->createElement( 'path' );
+ $arc->setAttribute( 'd', sprintf( 'M %.2F,%.2F A %.2F,%.2F 0 %d,0 %.2F,%.2F L %.2F,%.2F A %.2F,%2F 0 %d,1 %.2F,%.2F z',
+ // Endpoint low
+ $Xend, $Yend + $size,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Startpoint low
+ $Xstart, $Ystart + $size,
+ // Startpoint
+ $Xstart, $Ystart,
+ // Radius
+ $width, $height,
+ // SVG-Stuff
+ ( $endAngle - $startAngle ) > 180,
+ // Endpoint
+ $Xend, $Yend
+ )
+ );
+
+ $arc->setAttribute(
+ 'style',
+ $this->getStyle( $gradient, $filled )
+ );
+ $arc->setAttribute( 'id', $id = ( $this->options->idPrefix . 'CircularArc_' . ++$this->elementID ) );
+
+ $this->elements->appendChild( $arc );
+ }
+
+ return $id;
+ }
+
+ /**
+ * Draw circle
+ *
+ * @param ezcGraphCoordinate $center Center of ellipse
+ * @param mixed $width Width of ellipse
+ * @param mixed $height height of ellipse
+ * @param ezcGraphColor $color Color
+ * @param mixed $filled Filled
+ * @return void
+ */
+ public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true )
+ {
+ $this->createDocument();
+
+ $ellipse = $this->dom->createElement( 'ellipse' );
+ $ellipse->setAttribute( 'cx', sprintf( '%.4F', $center->x + $this->options->graphOffset->x ) );
+ $ellipse->setAttribute( 'cy', sprintf( '%.4F', $center->y + $this->options->graphOffset->y ) );
+ $ellipse->setAttribute( 'rx', sprintf( '%.4F', $width / 2 - ( $filled ? 0 : .5 ) ) );
+ $ellipse->setAttribute( 'ry', sprintf( '%.4F', $height / 2 - ( $filled ? 0 : .5 ) ) );
+
+ $ellipse->setAttribute(
+ 'style',
+ $this->getStyle( $color, $filled, 1 )
+ );
+
+ $ellipse->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Circle_' . ++$this->elementID ) );
+ $this->elements->appendChild( $ellipse );
+
+ return $id;
+ }
+
+ /**
+ * Draw an image
+ *
+ * The image will be inlined in the SVG document using data URL scheme. For
+ * this the mime type and base64 encoded file content will be merged to
+ * URL.
+ *
+ * @param mixed $file Image file
+ * @param ezcGraphCoordinate $position Top left position
+ * @param mixed $width Width of image in destination image
+ * @param mixed $height Height of image in destination image
+ * @return void
+ */
+ public function drawImage( $file, ezcGraphCoordinate $position, $width, $height )
+ {
+ $this->createDocument();
+
+ $data = getimagesize( $file );
+ $image = $this->dom->createElement( 'image' );
+
+ $image->setAttribute( 'x', sprintf( '%.4F', $position->x + $this->options->graphOffset->x ) );
+ $image->setAttribute( 'y', sprintf( '%.4F', $position->y + $this->options->graphOffset->y ) );
+ $image->setAttribute( 'width', sprintf( '%.4Fpx', $width ) );
+ $image->setAttribute( 'height', sprintf( '%.4Fpx', $height ) );
+ $image->setAttributeNS(
+ 'http://www.w3.org/1999/xlink',
+ 'xlink:href',
+ sprintf( 'data:%s;base64,%s',
+ $data['mime'],
+ base64_encode( file_get_contents( $file ) )
+ )
+ );
+
+ $this->elements->appendChild( $image );
+ $image->setAttribute( 'id', $id = ( $this->options->idPrefix . 'Image_' . ++$this->elementID ) );
+
+ return $id;
+ }
+
+ /**
+ * Return mime type for current image format
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return 'image/svg+xml';
+ }
+
+ /**
+ * Render image directly to output
+ *
+ * The method renders the image directly to the standard output. You
+ * normally do not want to use this function, because it makes it harder
+ * to proper cache the generated graphs.
+ *
+ * @return void
+ */
+ public function renderToOutput()
+ {
+ $this->createDocument();
+ $this->drawAllTexts();
+
+ header( 'Content-Type: ' . $this->getMimeType() );
+ echo $this->dom->saveXML();
+ }
+
+ /**
+ * Finally save image
+ *
+ * @param string $file Destination filename
+ * @return void
+ */
+ public function render( $file )
+ {
+ $this->createDocument();
+ $this->drawAllTexts();
+
+ // Embed used glyphs
+ $this->font->addFontToDocument( $this->dom );
+ $this->dom->save( $file );
+ }
+
+ /**
+ * Get resource of rendered result
+ *
+ * Return the resource of the rendered result. You should not use this
+ * method before you called either renderToOutput() or render(), as the
+ * image may not be completely rendered until then.
+ *
+ * @return DOMDocument
+ */
+ public function getResource()
+ {
+ return $this->dom;
+ }
+}
+
+?>
diff --git a/include/ezcomponents/Graph/src/driver/svg_font.php b/include/ezcomponents/Graph/src/driver/svg_font.php
new file mode 100644
index 000000000..eb1d332c8
--- /dev/null
+++ b/include/ezcomponents/Graph/src/driver/svg_font.php
@@ -0,0 +1,299 @@
+`.
+ *
+ * Usage:
+ *
+ * $font = new ezcGraphSvgFont();
+ * var_dump(
+ * $font->calculateStringWidth( '../tests/data/font.svg', 'Just a test string.' ),
+ * $font->calculateStringWidth( '../tests/data/font2.svg', 'Just a test string.' )
+ * );
+ *
+ *
+ * @version 1.3
+ * @package Graph
+ * @mainclass
+ */
+class ezcGraphSvgFont
+{
+ /**
+ * Units per EM
+ *
+ * @var float
+ */
+ protected $unitsPerEm;
+
+ /**
+ * Used glyphs
+ *
+ * @var array
+ */
+ protected $usedGlyphs = array();
+
+ /**
+ * Cache for glyph size to save XPath lookups.
+ *
+ * @var array
+ */
+ protected $glyphCache = array();
+
+ /**
+ * Used kernings
+ *
+ * @var array
+ */
+ protected $usedKerns = array();
+
+ /**
+ * Path to font
+ *
+ * @var string
+ */
+ protected $fonts = array();
+
+ /**
+ * Initialize SVG font
+ *
+ * Loads the SVG font $filename. This should be the path to the file
+ * generated by ttf2svg.
+ *
+ * Returns the (normlized) name of the initilized font.
+ *
+ * @param string $fontPath
+ * @return string
+ */
+ protected function initializeFont( $fontPath )
+ {
+ $fontPath = realpath( $fontPath );
+ if ( isset( $this->fonts[$fontPath] ) )
+ {
+ return $fontPath;
+ }
+
+ // Check for existance of font file
+ if ( !is_file( $fontPath ) || !is_readable( $fontPath ) )
+ {
+ throw new ezcBaseFileNotFoundException( $fontPath );
+ }
+
+ $this->fonts[$fontPath] = simplexml_load_file( $fontPath )->defs->font;
+
+ // SimpleXML requires us to register a namespace for XPath to work
+ $this->fonts[$fontPath]->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' );
+
+ // Extract the number of units per Em
+ $this->unitsPerEm[$fontPath] = (int) $this->fonts[$fontPath]->{'font-face'}['units-per-em'];
+
+ return $fontPath;
+ }
+
+ /**
+ * Get name of font
+ *
+ * Get the name of the given font, by extracting its font family from the
+ * SVG font file.
+ *
+ * @param string $fontPath
+ * @return string
+ */
+ public static function getFontName( $fontPath )
+ {
+ $font = simplexml_load_file( $fontPath )->defs->font;
+
+ // SimpleXML requires us to register a namespace for XPath to work
+ $font->registerXPathNamespace( 'svg', 'http://www.w3.org/2000/svg' );
+
+ // Extract the font family name
+ return (string) $font->{'font-face'}['font-family'];
+ }
+
+ /**
+ * XPath has no standard means of escaping ' and ", with the only solution
+ * being to delimit your string with the opposite type of quote. ( And if
+ * your string contains both concat( ) it ).
+ *
+ * This method will correctly delimit $char with the appropriate quote type
+ * so that it can be used in an XPath expression.
+ *
+ * @param string $char
+ * @return string
+ */
+ protected static function xpathEscape( $char )
+ {
+ return "'" . str_replace(
+ array( '\'', '\\' ),
+ array( '\\\'', '\\\\' ),
+ $char ) . "'";
+ }
+
+ /**
+ * Returns the
+ * // Retreive the predefined handler classes
+ * $originalHandlers = ezcImageAnalyzer::getHandlerClasses();
+ * foreach ( $handlerClasses as $id => $handlerClass )
+ * {
+ * // Unset the ezcImageAnalyzerPhpHandler (do not use that anymore!)
+ * if ( $handlerClass === 'ezcImageAnalyzerPhpHandler' )
+ * {
+ * unset( $handlerClasses[$id] );
+ * }
+ * }
+ * // Set the new collection of handler classes.
+ * ezcImageAnalyzer::setHandlerClasses( $handlerClasses );
+ *
+ * // Somewhere else in the code... This now tries to use your handler in the
+ * // first place
+ * $image = new ezcImageAnalyzer( '/var/cache/images/toby.jpg' );
+ *
+ *
+ * Or you can define your own handler classes to be used (beware, those must
+ * either be already loaded or load automatically on access).
+ *
+ * // Define your onw handler class to be used in the first place and fall back on
+ * // ImageMagick, if necessary.
+ * $handlerClasses = array( 'MyOwnHandlerClass', 'ezcImageAnalyzerImagemagickHandler' );
+ * ezcImageAnalyzer::setHandlerClasses( $handlerClasses );
+ *
+ * // Somewehre else in the code... This now tries to use your handler in the
+ * // first place
+ * $image = new ezcImageAnalyzer( '/var/cache/images/toby.jpg' );
+ *
+ *
+ * @property-read string $mime
+ * The MIME type of the image.
+ * @property-read ezcImageAnalyzerData $data
+ * Extended data about the image.
+ *
+ * @package ImageAnalysis
+ * @version 1.1.3
+ */
+class ezcImageAnalyzer
+{
+ /**
+ * The path of the file to analyze.
+ *
+ * @var string
+ */
+ protected $filePath;
+
+ /**
+ * Determines whether the image file has been analyzed or not.
+ * This is used internally.
+ *
+ * @var bool
+ */
+ protected $isAnalyzed;
+
+ /**
+ * Container to hold the properties
+ *
+ * @var array(string=>mixed)
+ */
+ protected $properties;
+
+ /**
+ * Collection of known handler classes. Classes are ordered by priority.
+ *
+ * @var array(string=>mixed)
+ */
+ protected static $knownHandlers = array(
+ 'ezcImageAnalyzerPhpHandler' => array(),
+ 'ezcImageAnalyzerImagemagickHandler' => array(),
+ );
+
+ /**
+ * Available handler classes and their options.
+ *
+ * @var array
+ */
+ protected static $availableHandlers;
+
+ /**
+ * Create an image analyzer for the specified file.
+ *
+ * @throws ezcBaseFilePermissionException
+ * If image file is not readable.
+ * @throws ezcBaseFileNotFoundException
+ * If image file does not exist.
+ * @throws ezcImageAnalyzerFileNotProcessableException
+ * If the file could not be processed.
+ * @param string $file The file to analyze.
+ */
+ public function __construct( $file )
+ {
+ if ( !file_exists( $file ) || !is_file( $file ) )
+ {
+ throw new ezcBaseFileNotFoundException( $file );
+ }
+ if ( !is_readable( $file ) )
+ {
+ throw new ezcBaseFilePermissionException( $file, ezcBaseFileException::READ );
+ }
+ $this->filePath = $file;
+ $this->isAnalyzed = false;
+
+ $this->checkHandlers();
+
+ $this->analyzeType();
+ }
+
+ /**
+ * Check all known handlers for availability.
+ *
+ * This method checks all registered handler classes for if the they are
+ * available (using {@link ezcImageAnalyzerHandler::isAvailable()}).
+ *
+ * @throws ezcImageAnalyzerInvalidHandlerException
+ * If a registered handler class does not exist
+ * or does not inherit from {@link ezcImageAnalyzerHandler}.
+ */
+ protected function checkHandlers()
+ {
+ if ( isset( ezcImageAnalyzer::$availableHandlers ) && is_array( ezcImageAnalyzer::$availableHandlers ) )
+ {
+ return;
+ }
+ ezcImageAnalyzer::$availableHandlers = array();
+ foreach ( ezcImageAnalyzer::$knownHandlers as $handlerClass => $options )
+ {
+ if ( !ezcBaseFeatures::classExists( $handlerClass ) || !is_subclass_of( $handlerClass, 'ezcImageAnalyzerHandler' ) )
+ {
+ throw new ezcImageAnalyzerInvalidHandlerException( $handlerClass );
+ }
+ $handler = new $handlerClass( $options );
+ if ( $handler->isAvailable() )
+ {
+ ezcImageAnalyzer::$availableHandlers[] = clone( $handler );
+ }
+ }
+ }
+
+ /**
+ * Returns an array of known handler classes.
+ *
+ * This method returns an array of available handler classes. The array is
+ * indexed by the handler names, which are assigned to an array of options
+ * set for this handler.
+ *
+ * @return array(string=>array(string=>string)) Handlers and options.
+ */
+ public static function getHandlerClasses()
+ {
+ return ezcImageAnalyzer::$knownHandlers;
+ }
+
+ /**
+ * Set the array of known handlers.
+ *
+ * Sets the available handlers. The array submitted must be indexed by
+ * the handler classes names (attention: handler classes must extend
+ * ezcImageAnalyzerHandler), assigned to an array of options for this
+ * handler. Most handlers don't have any options. Which options a handler
+ * may accept depends on the handler implementation.
+ *
+ * @param array(string=>array(string=>string)) $handlerClasses Handlers
+ * and options.
+ */
+ public static function setHandlerClasses( array $handlerClasses )
+ {
+ ezcImageAnalyzer::$knownHandlers = $handlerClasses;
+ ezcImageAnalyzer::$availableHandlers = null;
+ }
+
+ /**
+ * Sets the property $name to $value.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If the property does not exist.
+ * @throws ezcBasePropertyPermissionException
+ * If the property cannot be modified.
+ * @param string $name
+ * @param mixed $value
+ * @ignore
+ */
+ public function __set( $name, $value )
+ {
+ switch ( $name )
+ {
+ case 'mime':
+ case 'data':
+ throw new ezcBasePropertyPermissionException( $name, ezcBasePropertyPermissionException::READ );
+ default:
+ throw new ezcBasePropertyNotFoundException( $name );
+ }
+ }
+
+ /**
+ * Returns the property $name.
+ *
+ * @throws ezcBasePropertyNotFoundException
+ * If the property does not exist.
+ * @param string $name Name of the property to access.
+ * @return mixed Value of the desired property.
+ * @ignore
+ */
+ public function __get( $name )
+ {
+ switch ( $name )
+ {
+ case 'mime':
+ return $this->properties['mime'];
+ case 'data':
+ if ( !$this->isAnalyzed )
+ {
+ $this->analyzeImage();
+ }
+ return $this->properties[$name];
+ default:
+ throw new ezcBasePropertyNotFoundException( $name );
+ }
+ }
+
+ /**
+ * Checks if the property $name exist and returns the result.
+ *
+ * @param string $name
+ * @return bool
+ * @ignore
+ */
+ public function __isset( $name )
+ {
+ switch ( $name )
+ {
+ case 'mime':
+ case 'data':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Analyze the image file's MIME type.
+ * This method triggers a handler to analyze the MIME type of the given image file.
+ *
+ * @throws ezcImageAnalyzerFileNotProcessableException
+ * If the no handler is capable to analyze the given image file.
+ */
+ public function analyzeType()
+ {
+ if ( !is_array( ezcImageAnalyzer::$availableHandlers ) )
+ {
+ $this->checkHandlers();
+ }
+ foreach ( ezcImageAnalyzer::$availableHandlers as $handler )
+ {
+ if ( ( $mime = $handler->analyzeType( $this->filePath ) ) !== false )
+ {
+ $this->properties['mime'] = $mime;
+ return;
+ }
+ }
+ throw new ezcImageAnalyzerFileNotProcessableException( $this->filePath, "Could not determine MIME type of file." );
+ }
+
+ /**
+ * Analyze the image file.
+ *
+ * This method triggers a handler to analyze the given image file for more data.
+ *
+ * @throws ezcImageAnalyzerFileNotProcessableException
+ * If the no handler is capable to analyze the given image file.
+ * @throws ezcBaseFileIoException
+ * If an error occurs while the file is read.
+ */
+ public function analyzeImage()
+ {
+ if ( !is_array( ezcImageAnalyzer::$availableHandlers ) )
+ {
+ $this->checkHandlers();
+ }
+ if ( !isset( $this->properties['mime'] ) )
+ {
+ $this->analyzeType();
+ }
+ foreach ( ezcImageAnalyzer::$availableHandlers as $handler )
+ {
+ if ( $handler->canAnalyze( $this->properties['mime'] ) )
+ {
+ $this->properties['data'] = $handler->analyzeImage( $this->filePath );
+ $this->isAnalyzed = true;
+ return;
+ }
+ }
+ throw new ezcImageAnalyzerFileNotProcessableException( $this->filePath, "No handler found to analyze MIME type '{$this->mime}'." );
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageAnalysis/src/exceptions/exception.php b/include/ezcomponents/ImageAnalysis/src/exceptions/exception.php
new file mode 100644
index 000000000..c68f4385d
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/src/exceptions/exception.php
@@ -0,0 +1,20 @@
+
diff --git a/include/ezcomponents/ImageAnalysis/src/exceptions/file_not_processable.php b/include/ezcomponents/ImageAnalysis/src/exceptions/file_not_processable.php
new file mode 100644
index 000000000..f83343dd0
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/src/exceptions/file_not_processable.php
@@ -0,0 +1,37 @@
+
diff --git a/include/ezcomponents/ImageAnalysis/src/exceptions/invalid_handler.php b/include/ezcomponents/ImageAnalysis/src/exceptions/invalid_handler.php
new file mode 100644
index 000000000..9c49ced8f
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/src/exceptions/invalid_handler.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/ImageAnalysis/src/handlers/imagemagick.php b/include/ezcomponents/ImageAnalysis/src/handlers/imagemagick.php
new file mode 100644
index 000000000..b8efe96b1
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/src/handlers/imagemagick.php
@@ -0,0 +1,637 @@
+string)
+ */
+ protected $mimeMap = array(
+ 'bmp' => 'image/bmp',
+ 'bmp2' => 'image/bmp',
+ 'bmp3' => 'image/bmp',
+ 'cur' => 'image/x-win-bitmap',
+ 'dcx' => 'image/dcx',
+ 'epdf' => 'application/pdf',
+ 'epi' => 'application/postscript',
+ 'eps' => 'application/postscript',
+ 'eps2' => 'application/postscript',
+ 'eps3' => 'application/postscript',
+ 'epsf' => 'application/postscript',
+ 'epsi' => 'application/postscript',
+ 'ept' => 'application/postscript',
+ 'ept2' => 'application/postscript',
+ 'ept3' => 'application/postscript',
+ 'fax' => 'image/g3fax',
+ 'fits' => 'image/x-fits',
+ 'g3' => 'image/g3fax',
+ 'gif' => 'image/gif',
+ 'gif87' => 'image/gif',
+ 'icb' => 'application/x-icb',
+ 'ico' => 'image/x-win-bitmap',
+ 'icon' => 'image/x-win-bitmap',
+ 'jng' => 'image/jng',
+ 'jpeg' => 'image/jpeg',
+ 'jpg' => 'image/jpeg',
+ 'm2v' => 'video/mpeg2',
+ 'miff' => 'application/x-mif',
+ 'mng' => 'video/mng',
+ 'mpeg' => 'video/mpeg',
+ 'mpg' => 'video/mpeg',
+ 'otb' => 'image/x-otb',
+ 'p7' => 'image/x-xv',
+ 'palm' => 'image/x-palm',
+ 'pbm' => 'image/pbm',
+ 'pcd' => 'image/pcd',
+ 'pcds' => 'image/pcd',
+ 'pcl' => 'application/pcl',
+ 'pct' => 'image/pict',
+ 'pcx' => 'image/x-pcx',
+ 'pdb' => 'application/vnd.palm',
+ 'pdf' => 'application/pdf',
+ 'pgm' => 'image/x-pgm',
+ 'picon' => 'image/xpm',
+ 'pict' => 'image/pict',
+ 'pjpeg' => 'image/pjpeg',
+ 'png' => 'image/png',
+ 'png24' => 'image/png',
+ 'png32' => 'image/png',
+ 'png8' => 'image/png',
+ 'pnm' => 'image/pbm',
+ 'ppm' => 'image/x-ppm',
+ 'ps' => 'application/postscript',
+ 'psd' => 'image/x-photoshop',
+ 'ptif' => 'image/x-ptiff',
+ 'ras' => 'image/ras',
+ 'sgi' => 'image/sgi',
+ 'sun' => 'image/ras',
+ 'svg' => 'image/svg+xml',
+ 'svgz' => 'image/svg',
+ 'text' => 'text/plain',
+ 'tga' => 'image/tga',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'txt' => 'text/plain',
+ 'vda' => 'image/vda',
+ 'viff' => 'image/x-viff',
+ 'vst' => 'image/vst',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'xbm' => 'image/x-xbitmap',
+ 'xpm' => 'image/x-xbitmap',
+ 'xv' => 'image/x-viff',
+ 'xwd' => 'image/xwd',
+ );
+
+ /**
+ * MIME types this handler is capable to read.
+ *
+ * This array holds an extract of the
+ * {@link ezcImageAnalyzerHandler::$mimeMap}, listing all MIME types this
+ * handler is capable to analyze. The map is indexed by the MIME type,
+ * assigned to boolean true, to speed up hash lookups.
+ *
+ * @var array(string=>bool)
+ */
+ protected $mimeTypes = array(
+ 'application/pcl' => true,
+ 'application/pdf' => true,
+ 'application/postscript' => true,
+ 'application/vnd.palm' => true,
+ 'application/x-icb' => true,
+ 'application/x-mif' => true,
+ 'image/dcx' => true,
+ 'image/g3fax' => true,
+ 'image/gif' => true,
+ 'image/jng' => true,
+ 'image/jpeg' => true,
+ 'image/pbm' => true,
+ 'image/pcd' => true,
+ 'image/pict' => true,
+ 'image/pjpeg' => true,
+ 'image/png' => true,
+ 'image/ras' => true,
+ 'image/sgi' => true,
+ 'image/svg' => true,
+ 'image/tga' => true,
+ 'image/tiff' => true,
+ 'image/vda' => true,
+ 'image/vnd.wap.wbmp' => true,
+ 'image/vst' => true,
+ 'image/x-fits' => true,
+ 'image/x-ms-bmp' => true,
+ 'image/x-otb' => true,
+ 'image/x-palm' => true,
+ 'image/x-pcx' => true,
+ 'image/x-pgm' => true,
+ 'image/x-photoshop' => true,
+ 'image/x-ppm' => true,
+ 'image/x-ptiff' => true,
+ 'image/x-viff' => true,
+ 'image/x-win-bitmap' => true,
+ 'image/x-xbitmap' => true,
+ 'image/x-xv' => true,
+ 'image/xpm' => true,
+ 'image/xwd' => true,
+ 'text/plain' => true,
+ 'video/mng' => true,
+ 'video/mpeg' => true,
+ 'video/mpeg2' => true,
+ );
+
+ /**
+ * Analyzes the image type.
+ * This method analyzes image data to determine the MIME type. This method
+ * returns the MIME type of the file to analyze in lowercase letters (e.g.
+ * "image/jpeg") or false, if the images MIME type could not be determined.
+ *
+ * For a list of image types this handler will be able to analyze, see
+ * {@link ezcImageAnalyzerImagemagickHandler}.
+ *
+ * @param string $file The file to analyze.
+ * @return string|bool The MIME type if analyzation suceeded or false.
+ */
+ public function analyzeType( $file )
+ {
+ $parameters = '-format ' . escapeshellarg( '%m|' ) . ' ' . escapeshellarg( $file );
+ $res = ezcImageAnalyzerImagemagickHandler::runCommand( $parameters, $outputString, $errorString );
+ if ( $res !== 0 || $errorString !== '' )
+ {
+ return false;
+ }
+ $identifiers = explode( '|', strtolower( $outputString ), 2 );
+ if ( !isset( $this->mimeMap[$identifiers[0]] ) )
+ {
+ return false;
+ }
+ return $this->mimeMap[$identifiers[0]];
+ }
+
+ /**
+ * Analyze the image for detailed information.
+ *
+ * This may return various information about the image, depending on it's
+ * type. All information is collected in the struct
+ * {@link ezcImageAnalyzerData}. At least the
+ * {@link ezcImageAnalyzerData::$mime} attribute is always available, if the
+ * image type can be analyzed at all. Additionally this handler will always
+ * set the {@link ezcImageAnalyzerData::$width},
+ * {@link ezcImageAnalyzerData::$height} and
+ * {@link ezcImageAnalyzerData::$size} attributes. For detailes information
+ * on the additional data returned, see {@link ezcImageAnalyzerImagemagickHandler}.
+ *
+ * @todo Why does ImageMagick return the wrong file size on TIFF with comments?
+ * @todo Check for translucent transparency.
+ *
+ * @throws ezcImageAnalyzerFileNotProcessableException
+ * If image file can not be processed.
+ * @param string $file The file to analyze.
+ * @return ezcImageAnalyzerData
+ */
+ public function analyzeImage( $file )
+ {
+ // Example strings returned here:
+ // JPEG (Exif without comment):
+ // string(45) "[JPEG|76383|399|600|8|59428|DirectClassRGB|]*"
+ // --------------------------------
+ // TIFF (Exif with comment):
+ // string(79) "[TIFF|108125|399|600|8|113524|DirectClassRGB|A simple comment in a TIFF file.]*"
+ // --------------------------------
+ // PNG:
+ // string(46) "[PNG|5420|160|120|8|254|DirectClassRGBMatte|]*"
+ // --------------------------------
+ // GIF (Animated):
+ // string(168) "[GIF|4100|80|50|8|38|PseudoClassRGB|]*[GIF|4100|80|50|8|21|PseudoClassRGB|]*[GIF|4100|80|50|8|17|PseudoClassRGB|Copyright 1996 DeMorgan Industries Corp.
+ //
+ // Animated Cog]*"
+ // --------------------------------
+
+ $command = '-format ' . escapeshellarg( '[%m|%b|%w|%h|%k|%r|%c]*' ) . ' ' . escapeshellarg( $file );
+
+ // Execute ImageMagick
+ $return = $this->runCommand( $command, $outputString, $errorString );
+ if ( $return !== 0 || $errorString !== '' )
+ {
+ throw new ezcImageAnalyzerFileNotProcessableException( $file, "ImageMagick error: '{$errorString}'." );
+ }
+
+ $dataStruct = new ezcImageAnalyzerData();
+
+ $rawDataArr = explode( '*', $outputString );
+ if ( sizeof( $rawDataArr ) === 1 )
+ {
+ throw new ezcImageAnalyzerFileNotProcessableException( $file, "ImageMagick did not return correct formated string." );
+ }
+
+ // Unset last (empty) element
+ unset( $rawDataArr[count( $rawDataArr ) - 1] );
+
+ if ( sizeof( $rawDataArr ) > 1 )
+ {
+ $dataStruct->isAnimated = true;
+ }
+ foreach ( $rawDataArr as $id => $rawData )
+ {
+ $parsedData = explode( '|', substr( $rawData, 1, -1 ) );
+ $dataStruct->mime = $this->mimeMap[strtolower( $parsedData[0] )];
+
+ $dataStruct->size = filesize( $file );
+
+ $dataStruct->width = max( (int) $parsedData[2], $dataStruct->width );
+ $dataStruct->height = max( (int) $parsedData[3], $dataStruct->height );
+
+ $dataStruct->isColor = $parsedData[4] > 2 ? true : false;
+
+ $dataStruct->transparencyType = self::TRANSPARENCY_OPAQUE;
+ if ( strpos( $parsedData[5], 'RGBMatte' ) !== FALSE )
+ {
+ $dataStruct->transparencyType = self::TRANSPARENCY_TRANSPARENT;
+ }
+
+ if ( $parsedData[6] !== '' )
+ {
+ if ( $dataStruct->isAnimated && $id > 0 )
+ {
+ $dataStruct->commentList[] = $parsedData[6];
+ }
+ else
+ {
+ $dataStruct->comment = $parsedData[6];
+ $dataStruct->commentList = array( $parsedData[6] );
+ }
+ }
+
+ if ( $dataStruct->mime === 'image/jpeg' || $dataStruct->mime === 'image/tiff' )
+ {
+ $this->analyzeExif( $dataStruct, $file );
+ }
+ }
+ return $dataStruct;
+ }
+
+ /**
+ * Analyze Exif data contained in JPEG and TIFF images.
+ *
+ * This method analyzes the Exif data contained in JPEG and TIFF images,
+ * using ImageMagick's "identify" binary.
+ *
+ * This method tries to provide the EXIF data in a format as close as
+ * possible to the format returned by ext/EXIF {@link http://php.net/exif}.
+ *
+ * @param ezcImageAnalyzerData $data The data object to fill.
+ * @param string $file The file to analyze.
+ */
+ protected function analyzeExif( ezcImageAnalyzerData $data, $file )
+ {
+ $tagMap = array(
+ "IFD0" => array(
+ "ImageDescription",
+ "Make",
+ "Model",
+ "Orientation",
+ "XResolution",
+ "YResolution",
+ "ResolutionUnit",
+ "Software",
+ "DateTime",
+ "YCbCrPositioning",
+ "Exif_IFD_Pointer",
+ "Copyright",
+ "UserComment",
+ ),
+
+ "EXIF" => array(
+ "ExposureTime",
+ "FNumber",
+ "ExposureProgram",
+ "ISOSpeedRatings",
+ "ExifVersion",
+ "DateTimeOriginal",
+ "DateTimeDigitized",
+ "ComponentsConfiguration",
+ "BrightnessValue",
+ "ExposureBiasValue",
+ "MaxApertureValue",
+ "MeteringMode",
+ "LightSource",
+ "Flash",
+ "FocalLength",
+ // ImageMagick does not grab this correct, therefore not supported
+ // "SubjectLocation",
+ "MakerNote",
+ "UserComment",
+ "FlashPixVersion",
+ "ColorSpace",
+ "ExifImageWidth",
+ "ExifImageLength",
+ "InteroperabilityOffset",
+ "FileSource",
+ "SceneType",
+ "CustomRendered",
+ "ExposureMode",
+ "WhiteBalance",
+ "DigitalZoomRatio",
+ "FocalLengthIn35mmFilm",
+ "SceneCaptureType",
+ "GainControl",
+ "Contrast",
+ "Saturation",
+ "Sharpness",
+ "SubjectDistanceRange",
+ ),
+ "INTEROP" => array(
+ "InterOperabilityIndex",
+ "InterOperabilityVersion"
+ )
+ );
+
+ // Retreive exif data
+ $command = '-format ' . escapeshellarg( "%[EXIF:*]" ) . ' ' . escapeshellarg( $file );
+ $return = $this->runCommand( $command, $outputString, $errorString, false );
+ if ( $return !== 0 || $errorString !== '' )
+ {
+ throw new ezcImageAnalyzerFileNotProcessableException( $file, "ImageMagick error: '{$errorString}'." );
+ }
+
+ // The following is done in 2 steps to ensure the same array order as ext/exif provides.
+
+ // Pre-process data
+ $rawData = explode( "\n", $outputString );
+ $dataArr = array();
+ foreach ( $rawData as $dataString )
+ {
+ $dataParts = explode( "=", $dataString, 2 );
+ if ( sizeof( $dataParts ) === 2 )
+ {
+ $dataArr[$dataParts[0]] = substr( $dataParts[1], -1, 1 ) === "." ? substr( $dataParts[1], 0, -1 ) : $dataParts[1];
+ }
+ }
+ // Some post-processing is needed because ext/exif has some different tag names
+ if ( isset( $dataArr["ExifOffset"] ) )
+ {
+ $dataArr["Exif_IFD_Pointer"] = $dataArr["ExifOffset"];
+ }
+ if ( isset( $dataArr["InteroperabilityIndex"] ) )
+ {
+ $dataArr["InterOperabilityIndex"] = $dataArr["InteroperabilityIndex"];
+ }
+ if ( isset( $dataArr["InteroperabilityVersion"] ) )
+ {
+ $dataArr["InterOperabilityVersion"] = $dataArr["InteroperabilityVersion"];
+ }
+ if ( isset( $dataArr["Artist"] ) )
+ {
+ $dataArr["Author"] = $dataArr["Artist"];
+ }
+
+ // Assign data to tags
+ $exifArr = array();
+ foreach ( $tagMap as $section => $tags )
+ {
+ foreach ( $tags as $tag )
+ {
+ if ( isset( $dataArr[$tag] ) )
+ {
+ // Correct types
+ switch ( true )
+ {
+ case ( ctype_digit( $dataArr[$tag] ) && stripos( $tag, "version" ) === false ):
+ $exifArr[$section][$tag] = (int)$dataArr[$tag];
+ break;
+ case ( is_numeric( $dataArr[$tag] ) && stripos( $tag, "version" ) === false ):
+ $exifArr[$section][$tag] = (float)$dataArr[$tag];
+ break;
+ default:
+ $exifArr[$section][$tag] = $dataArr[$tag];
+ break;
+ }
+ }
+ }
+ }
+
+ // Retreive additional data for computation
+ $imageData = getimagesize( $file );
+
+ $colorCount = 0;
+ $command = '-format ' . escapeshellarg( '%k' ) . ' ' . escapeshellarg( $file );
+ $return = $this->runCommand( $command, $colorCount, $errorString );
+ if ( $return !== 0 || $errorString !== '' )
+ {
+ throw new ezcImageAnalyzerFileNotProcessableException( $file, "ImageMagick error: '{$errorString}'." );
+ }
+
+ // Compute additional section ext/EXIF provides
+ $additionsArr = array();
+ $addtionsArr["FILE"]["FileName"] = basename( $file );
+ $addtionsArr["FILE"]["FileDateTime"] = filemtime( $file );
+ $addtionsArr["FILE"]["FileSize"] = filesize( $file );
+ $addtionsArr["FILE"]["FileType"] = $imageData[2];
+ $addtionsArr["FILE"]["MimeType"] = $data->mime;
+ $addtionsArr["FILE"]["SectionsFound"] =
+ ( isset( $exifArr["EXIF"] ) || isset( $exifArr["IFD0"] ) ? "ANY_TAG, " : "" )
+ . implode( ", ", array_keys( $exifArr ) );
+
+ $addtionsArr["COMPUTED"]["html"] = "width=\"{$data->width}\" height=\"{$data->height}\"";
+ $addtionsArr["COMPUTED"]["Height"] = $data->height;
+ $addtionsArr["COMPUTED"]["Width"] = $data->width;
+ $addtionsArr["COMPUTED"]["IsColor"] = ( $colorCount < 3 ) ? 0 : 1;
+
+ // @todo Implement if possible!
+ // $addtionsArr["COMPUTED"]["ByteOrderMotorola"] = null;
+
+ $fNumberParts = isset( $exifArr["EXIF"]["FNumber"] ) ? explode( "/", $exifArr["EXIF"]["FNumber"] ) : null;
+ if ( sizeof( $fNumberParts ) === 2 )
+ {
+ $addtionsArr["COMPUTED"]["ApertureFNumber"] = sprintf( "f/%.1f", $fNumberParts[0] / $fNumberParts[1] );
+ }
+ // ImageMagick resturns "..." for not set comments
+ if ( isset( $exifArr["EXIF"]["UserComment"] ) )
+ {
+ $addtionsArr["COMPUTED"]["UserComment"] = preg_match( "/^\.*$/", $exifArr["EXIF"]["UserComment"] ) === false ? $exifArr["EXIF"]["UserComment"] : null;
+ // @todo Maybe we can determine that somehow?
+ // $addtionsArr["COMPUTED"]["UserCommentEncoding"] = "UNDEFINED";
+ }
+
+ // Not available through ImageMagick
+ // $addtionsArr["COMPUTED"]["Thumbnail.FileType"] = null
+ // $addtionsArr["COMPUTED"]["Thumbnail.MimeType"] = null
+
+ // Merge arrays (done here, to have consistent key order)
+ $data->exif = array_merge( $addtionsArr, $exifArr );
+ }
+
+ /**
+ * Returns if the handler can analyze a given MIME type.
+ *
+ * This method returns if the driver is capable of analyzing a given MIME
+ * type. This method should be called before trying to actually analyze an
+ * image using the drivers {@link ezcImageAnalyzerHandler::analyzeImage()}
+ * method.
+ *
+ * @param string $mime The MIME type to check for.
+ * @return bool True if the handler is able to analyze the MIME type.
+ */
+ public function canAnalyze( $mime )
+ {
+ return isset( $this->mimeTypes[strtolower( $mime )] );
+ }
+
+ /**
+ * Checks wether the GD handler is available on the system.
+ *
+ * Returns if PHP's {@link getimagesize()} function is available.
+ *
+ * @return bool True is the handler is available.
+ */
+ public function isAvailable()
+ {
+ if ( !isset( $this->isAvailable ) )
+ {
+ $this->isAvailable = $this->checkImagemagick();
+ }
+ return $this->isAvailable;
+ }
+
+ /**
+ * Checks the availability of ImageMagick on the system.
+ *
+ * @return bool
+ */
+ protected function checkImagemagick()
+ {
+ if ( !isset( $this->options['binary'] ) )
+ {
+ switch ( PHP_OS )
+ {
+ case 'Linux':
+ case 'Unix':
+ case 'FreeBSD':
+ case 'MacOS':
+ case 'Darwin':
+ case 'SunOS':
+ $this->binary = 'identify';
+ break;
+ case 'Windows':
+ case 'WINNT':
+ case 'WIN32':
+ $this->binary = 'identify.exe';
+ break;
+ default:
+ throw new ezcImageAnalyzerInvalidHandlerException( 'ezcImageAnalyzerImagemagickHandler' );
+ break;
+ }
+ }
+ else
+ {
+ $this->binary = $this->options['binary'];
+ }
+
+ return ezcBaseFeatures::hasImageIdentify();
+ }
+
+ /**
+ * Run the binary registered in ezcImageAnalyzerImagemagickHandler::$binary.
+ *
+ * This method executes the ImageMagick binary using the applied parameter
+ * string. It returns the return value of the command. The output printed
+ * to STDOUT and ERROUT is available through the $stdOut and $errOut
+ * parameters.
+ *
+ * @param string $parameters The parameters for the binary to execute.
+ * @param string $stdOut The standard output.
+ * @param string $errOut The error output.
+ * @param bool $stripNewlines Wether to strip the newlines from STDOUT.
+ * @return int The return value of the command (0 on success).
+ */
+ protected function runCommand( $parameters, &$stdOut, &$errOut, $stripNewlines = true )
+ {
+ $command = escapeshellcmd( $this->binary ) . ( $parameters !== '' ? ' ' . $parameters : '' );
+ // Prepare to run ImageMagick command
+ $descriptors = array(
+ array( 'pipe', 'r' ),
+ array( 'pipe', 'w' ),
+ array( 'pipe', 'w' ),
+ );
+
+ // Open ImageMagick process
+ $process = proc_open( $command, $descriptors, $pipes );
+
+ // Close STDIN pipe
+ fclose( $pipes[0] );
+
+ // Read STDOUT
+ $stdOut = '';
+ do
+ {
+ $stdOut .= ( $stripNewlines === true ) ? rtrim( fgets( $pipes[1], 1024), "\n" ) : fgets( $pipes[1], 1024 );
+ } while ( !feof( $pipes[1] ) );
+
+ // Read STDERR
+ $errOut = '';
+ do
+ {
+ $errOut .= rtrim( fgets( $pipes[2], 1024), "\n" );
+ } while ( !feof( $pipes[2] ) );
+
+ // Wait for process to terminate and store return value
+ return proc_close( $process );
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageAnalysis/src/handlers/php.php b/include/ezcomponents/ImageAnalysis/src/handlers/php.php
new file mode 100644
index 000000000..8b1c8650e
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/src/handlers/php.php
@@ -0,0 +1,458 @@
+width = $data[0];
+ $dataStruct->height = $data[1];
+ $dataStruct->mime = image_type_to_mime_type( $data[2] );
+ $dataStruct->size = filesize( $file );
+
+ if ( ( $dataStruct->mime === 'image/jpeg' || $dataStruct->mime === 'image/tiff' )
+ && ezcBaseFeatures::hasFunction( 'exif_read_data')
+ )
+ {
+ $this->analyzeExif( $file, $dataStruct );
+ }
+ elseif ( $dataStruct->mime === 'image/gif' )
+ {
+ $this->analyzeGif( $file, $dataStruct );
+ }
+
+ return $dataStruct;
+ }
+
+ /**
+ * Returns if the handler can analyze a given MIME type.
+ *
+ * This method returns if the driver is capable of analyzing a given MIME
+ * type. This method should be called before trying to actually analyze an
+ * image using the drivers {@link self::analyzeImage()} method.
+ *
+ * @param string $mime The MIME type to check for.
+ * @return bool True if the handler is able to analyze the MIME type.
+ */
+ public function canAnalyze( $mime )
+ {
+ switch ( $mime )
+ {
+ case 'image/gif':
+ case 'image/jpeg':
+ case 'image/png':
+ case 'image/psd':
+ case 'image/bmp':
+ case 'image/tiff':
+ case 'image/tiff':
+ case 'image/jp2':
+ case 'application/x-shockwave-flash':
+ case 'image/iff':
+ case 'image/vnd.wap.wbmp':
+ case 'image/xbm':
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Checks wether the GD handler is available on the system.
+ *
+ * Returns if PHP's {@link getimagesize()} function is available.
+ *
+ * @return bool True is the handler is available.
+ */
+ public function isAvailable()
+ {
+ return ezcBaseFeatures::hasFunction( 'getimagesize' );
+ }
+
+ /**
+ * Analyze EXIF enabled file format for EXIF data entries.
+ *
+ * The image file is analyzed by calling exif_read_data and placing the
+ * result in self::exif. In addition it fills in extra properties from
+ * the EXIF data for easy and uniform access.
+ *
+ * @param string $file The file to analyze.
+ * @param ezcImageAnalyzerData $dataStruct The data struct to fill.
+ * @return ezcImageAnalyzerData The filled data struct.
+ */
+ private function analyzeExif( $file, ezcImageAnalyzerData $dataStruct )
+ {
+ $dataStruct->exif = exif_read_data( $file, "COMPUTED,FILE", true, false );
+
+ // Section "COMPUTED"
+ if ( isset( $dataStruct->exif['COMPUTED']['Width'] ) && isset( $dataStruct->exif['COMPUTED']['Height'] ) )
+ {
+ $dataStruct->width = $dataStruct->exif['COMPUTED']['Width'];
+ $dataStruct->height = $dataStruct->exif['COMPUTED']['Height'];
+ }
+ if ( isset( $dataStruct->exif['COMPUTED']['IsColor'] ) )
+ {
+ $dataStruct->isColor = $dataStruct->exif['COMPUTED']['IsColor'] == 1;
+ }
+ if ( isset( $dataStruct->exif['COMPUTED']['UserComment'] ) )
+ {
+ $dataStruct->comment = $dataStruct->exif['COMPUTED']['UserComment'];
+ $dataStruct->commentList = array( $dataStruct->comment );
+ }
+ if ( isset( $dataStruct->exif['COMPUTED']['Copyright'] ) )
+ {
+ $dataStruct->copyright = $dataStruct->exif['COMPUTED']['Copyright'];
+ }
+
+ // Section THUMBNAIL
+ $dataStruct->hasThumbnail = isset( $dataStruct->exif['THUMBNAIL'] );
+
+ // Section "FILE"
+ if ( isset( $dataStruct->exif['FILE']['FileSize'] ) )
+ {
+ $dataStruct->size = $dataStruct->exif['FILE']['FileSize'];
+ }
+ if ( isset( $dataStruct->exif['FILE']['FileDateTime'] ) )
+ {
+ $dataStruct->date = $dataStruct->exif['FILE']['FileDateTime'];
+ }
+
+ // EXIF based image are never animated.
+ $dataStruct->isAnimated = false;
+
+ return $dataStruct;
+ }
+
+ /**
+ * Analyze GIF files for detailed information.
+ *
+ * The GIF file is analyzed by scanning for frame entries, if more than one
+ * is found it is assumed to be animated.
+ * It also extracts other information such as image width and height, color
+ * count, image mode, transparency type and comments.
+ *
+ * @throws ezcBaseFileIoException
+ * If image file could not be read.
+ * @throws ezcImageAnalyzerFileNotProcessableException
+ * If image file can not be processed.
+ * @param string $file The file to analyze.
+ * @param ezcImageAnalyzerData $dataStruct The data struct to fill.
+ * @return ezcImageAnalyzerData The filled data struct.
+ */
+ private function analyzeGif( $file, ezcImageAnalyzerData $dataStruct )
+ {
+ if ( ( $fp = fopen( $file, 'rb' ) ) === false )
+ {
+ throw new ezcBaseFileIoException( $file, ezcBaseFileException::READ );
+ }
+
+ // Read GIF header
+ $magic = fread( $fp, 6 );
+ $offset = 6;
+ if ( $magic != 'GIF87a' &&
+ $magic != 'GIF89a' )
+ {
+ throw new ezcImageAnalyzerFileNotProcessableException( $file, 'Not a valid GIF image file' );
+ }
+
+ $info = array();
+
+ $version = substr( $magic, 3 );
+ $frames = 0;
+ // Gifs are always indexed
+ $dataStruct->mode = self::MODE_INDEXED;
+ $dataStruct->commentList = array();
+ $dataStruct->transparencyType = self::TRANSPARENCY_OPAQUE;
+
+ // Read Logical Screen Descriptor
+ $data = unpack( "v1width/v1height/C1bitfield/C1index/C1ration", fread( $fp, 7 ) );
+ $offset += 7;
+
+ $lsdFields = $data['bitfield'];
+ $globalColorCount = 0;
+ $globalColorTableSize = 0;
+ if ( $lsdFields >> 7 )
+ {
+ // Extract 3 bits for color count
+ $globalColorCount = ( 1 << ( ( $lsdFields & 0x07 ) + 1) );
+ // Each color entry is RGB ie. 3 bytes
+ $globalColorTableSize = $globalColorCount * 3;
+ }
+
+ $dataStruct->colorCount = $globalColorCount;
+ $dataStruct->width = $data['width'];
+ $dataStruct->height = $data['height'];
+
+ if ( $globalColorTableSize )
+ {
+ // Skip the color table, we don't need the data
+ fseek( $fp, $globalColorTableSize, SEEK_CUR );
+ $offset += $globalColorTableSize;
+ }
+
+ $done = false;
+ // Iterate over all blocks and extract information
+ while ( !$done )
+ {
+ $data = fread( $fp, 1 );
+ $offset += 1;
+ $blockType = ord( $data[0] );
+
+ if ( $blockType == 0x21 ) // Extension Introducer
+ {
+ $data .= fread( $fp, 1 );
+ $offset += 1;
+ $extensionLabel = ord( $data[1] );
+
+ if ( $extensionLabel == 0xf9 ) // Graphical Control Extension
+ {
+ $data = unpack( "C1blocksize/C1bitfield/v1delay/C1index/C1term", fread( $fp, 5 + 1 ) );
+ $gceFlags = $data['bitfield'];//ord( $data[1] );
+ // $animationTimer is currently not in use.
+ /* $animationTimer = $data['delay']; */
+
+ // Check bit 0
+ if ( $gceFlags & 0x01 )
+ {
+ $dataStruct->transparencyType = self::TRANSPARENCY_TRANSPARENT;
+ }
+ $offset += 5 + 1;
+ }
+ else if ( $extensionLabel == 0xff ) // Application Extension
+ {
+ $data = fread( $fp, 12 );
+ $offset += 12;
+
+ $dataBlockDone = false;
+ while ( !$dataBlockDone )
+ {
+ $data = unpack( "C1blocksize", fread( $fp, 1 ) );
+ $offset += 1;
+ $blockBytes = $data['blocksize'];
+
+ if ( $blockBytes )
+ {
+ // Skip application data, we don't need the data
+ fseek( $fp, $blockBytes, SEEK_CUR );
+ $offset += $blockBytes;
+ }
+ else
+ {
+ $dataBlockDone = true;
+ }
+ }
+ }
+ else if ( $extensionLabel == 0xfe ) // Comment Extension
+ {
+ $commentBlockDone = false;
+ $comment = false;
+
+ while ( !$commentBlockDone )
+ {
+ $data = unpack( "C1blocksize", fread( $fp, 1 ) );
+ $offset += 1;
+ $blockBytes = $data['blocksize'];
+
+ if ( $blockBytes )
+ {
+ // Append current block to comment
+ $data = fread( $fp, $blockBytes );
+ $comment .= $data;
+ $offset += $blockBytes;
+ }
+ else
+ {
+ $commentBlockDone = true;
+ }
+ }
+ if ( $comment )
+ {
+ if ( $dataStruct->comment === null )
+ {
+ $dataStruct->comment = $comment;
+ }
+ $dataStruct->commentList[] = $comment;
+ }
+ }
+ else
+ {
+ throw new ezcImageAnalyzerFileNotProcessableException( $file, "Invalid extension label 0x" . hexdec( $extensionLabel ) . " in GIF image." );
+ }
+ }
+ else if ( $blockType == 0x2c ) // Image Descriptor
+ {
+ ++$frames;
+ $data .= fread( $fp, 9 );
+ $data = unpack( "C1separator/v1leftpos/v1toppos/v1width/v1height/C1bitfield", $data );
+ $localColorTableSize = 0;
+ $localColorCount = 0;
+ $idFields = $data['bitfield'];
+ if ( $idFields >> 7 ) // Local Color Table
+ {
+ // Extract 3 bits for color count
+ $localColorCount = ( 1 << ( ( $idFields & 0x07 ) + 1) );
+ // Each color entry is RGB ie. 3 bytes
+ $localColorTableSize = $localColorCount * 3;
+ }
+ if ( $localColorCount > $globalColorCount )
+ {
+ $dataStruct->colorCount = $localColorCount;
+ }
+
+ if ( $localColorTableSize )
+ {
+ // Skip the color table, we don't need the data
+ fseek( $fp, $localColorTableSize, SEEK_CUR );
+ $offset += $localColorTableSize;
+ }
+
+ $lzwCodeSize = fread( $fp, 1 ); // LZW Minimum Code Size, currently unused
+ $offset += 1;
+
+ $dataBlockDone = false;
+ while ( !$dataBlockDone )
+ {
+ $data = unpack( "C1blocksize", fread( $fp, 1 ) );
+ $offset += 1;
+ $blockBytes = $data['blocksize'];
+
+ if ( $blockBytes )
+ {
+ // Skip image data, we don't need the data
+ fseek( $fp, $blockBytes, SEEK_CUR );
+ $offset += $blockBytes;
+ }
+ else
+ {
+ $dataBlockDone = true;
+ }
+ }
+ }
+ else if ( $blockType == 0x3b ) // Trailer, end of stream
+ {
+ $done = true;
+ }
+ else
+ {
+ throw new ezcImageAnalyzerFileNotProcessableException( $file, "Invalid block type 0x" . hexdec( $blockType ) . " in GIF image." );
+ }
+ if ( feof( $fp ) )
+ {
+ break;
+ }
+ }
+ $dataStruct->isAnimated = $frames > 1;
+
+ return $dataStruct;
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageAnalysis/src/interfaces/handler.php b/include/ezcomponents/ImageAnalysis/src/interfaces/handler.php
new file mode 100644
index 000000000..c7aeadafd
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/src/interfaces/handler.php
@@ -0,0 +1,131 @@
+mixed)
+ */
+ protected $options = array();
+
+ /**
+ * Create an ezcImageAnalyzerHandler to analyze a file.
+ *
+ * The constructor can optionally receive an array of options. Which
+ * options are utilized by the handler depends on it's implementation.
+ * To determine this, please refer to the specific handler.
+ *
+ * @throws ezcImageAnalyzerException
+ * If the handler is not able to work.
+ * @param array $options Possible options for the handler.
+ */
+ public function __construct( array $options = array() )
+ {
+ $this->options = $options;
+ }
+
+ /**
+ * Checks wether the given handler is available for analyzing images.
+ *
+ * Each ezcImageAnalyzerHandler must implement this method in order to
+ * check if the handler is available on the system. The method has to
+ * return true, if the handle is currently available to analyze images
+ * (e.g. if the GD extension is available, for the
+ * {@link ezcImageAnalyzerPhpHandler}).
+ *
+ * @return bool True if the handler is available.
+ */
+ abstract public function isAvailable();
+
+ /**
+ * Analyzes the image type.
+ *
+ * This method analyzes image data to determine the MIME type. Each
+ * ezcImageAnalyzerHandler must at least be capable of performing this
+ * operation on a file. This method has to return the MIME type of the
+ * file to analyze in lowercase letters (e.g. "image/jpeg") or false, if
+ * the images MIME type could not be determined.
+ *
+ * @param string $file The file to analyze.
+ * @return string|bool The MIME type if analyzation suceeded or false.
+ */
+ abstract public function analyzeType( $file );
+
+ /**
+ * Analyze the image for detailed information.
+ *
+ * This may return various information about the image, depending on it's
+ * type and the implemented facilities of the handler. All information is
+ * collected in the struct {@link ezcImageAnalyzerData}. Which information
+ * is set about an image in the returned data struct, depends on the image
+ * type and the capabilities of the handler. At least the
+ * {@link ezcImageAnalyzerData::$mime} attribute must be set. Most handlers
+ * also provide additional information like the image dimensions and the size
+ * of the image file.
+ *
+ * @throws ezcImageAnalyzerFileNotProcessableException
+ * If image file can not be processed.
+ * @param string $file The file to analyze.
+ * @return ezcImageAnalyzerData
+ */
+ abstract public function analyzeImage( $file );
+
+ /**
+ * Returns if the handler can analyze a given MIME type.
+ *
+ * This method returns if the driver is capable of analyzing a given MIME
+ * type. This method should be called before trying to actually analyze an
+ * image using the drivers {@link self::analyzeImage()} method.
+ *
+ * @param string $mime The MIME type to check for.
+ * @return bool True if the handler is able to analyze the MIME type.
+ */
+ abstract public function canAnalyze( $mime );
+}
+?>
diff --git a/include/ezcomponents/ImageAnalysis/src/structs/analyzer_data.php b/include/ezcomponents/ImageAnalysis/src/structs/analyzer_data.php
new file mode 100644
index 000000000..743065907
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/src/structs/analyzer_data.php
@@ -0,0 +1,221 @@
+
+ * $analyzer = new ezcImageAnalyzer( 'myfile.jpg' );
+ * echo $analyzer->data->size;
+ *
+ *
+ * @see ezcImageAnalyzer
+ * @see ezcImageAnalyzerHandler
+ *
+ * @package ImageAnalysis
+ * @version 1.1.3
+ */
+class ezcImageAnalyzerData extends ezcBaseStruct
+{
+ /**
+ * Detected MIME type for the image.
+ *
+ * @var string
+ */
+ public $mime;
+
+ /**
+ * EXIF information retrieved from image.
+ *
+ * This will only be filled in for images which supports EXIF entries,
+ * currently they are:
+ * - image/jpeg
+ * - image/tiff
+ *
+ * @link http://php.net/manual/en/function.exif-read-data.php
+ *
+ * @var array(string=>string)
+ */
+ public $exif = array();
+
+ /**
+ * Width of image in pixels.
+ *
+ * @var int
+ */
+ public $width = 0;
+
+ /**
+ * Height of image in pixels.
+ *
+ * @var int
+ */
+ public $height = 0;
+
+ /**
+ * Size of image file in bytes.
+ *
+ * @var int
+ */
+ public $size = 0;
+
+ /**
+ * The image mode.
+ *
+ * Can be one of:
+ * - ezcImageAnalyzerHandler::MODE_INDEXED - Image is built with a palette and consists of
+ * indexed values per pixel.
+ * - ezcImageAnalyzerHandler::MODE_TRUECOLOR - Image consists of RGB value per pixel.
+ *
+ * @var int
+ */
+ public $mode = ezcImageAnalyzerHandler::MODE_TRUECOLOR;
+
+ /**
+ * Type of transparency in image.
+ *
+ * Can be one of:
+ * - ezcImageAnalyzerHandler::TRANSPARENCY_OPAQUE - No parts of image is transparent.
+ * - ezcImageAnalyzerHandler::TRANSPARENCY_TRANSPARENT - Selected palette entries are
+ * completely see-through.
+ * - ezcImageAnalyzerHandler::TRANSPARENCY_TRANSLUCENT - Transparency determined pixel per
+ * pixel with a fuzzy value.
+ *
+ * @var int
+ */
+ public $transparencyType;
+
+ /**
+ * Does the image have colors?
+ *
+ * @var bool
+ */
+ public $isColor = true;
+
+ /**
+ * Number of colors in image.
+ *
+ * @var int
+ */
+ public $colorCount = 0;
+
+ /**
+ * First inline comment for the image.
+ *
+ * @var string
+ */
+ public $comment = null;
+
+ /**
+ * List of inline comments for the image.
+ *
+ * @var array(string)
+ */
+ public $commentList = array();
+
+ /**
+ * Copyright text for the image.
+ *
+ * @var string
+ */
+ public $copyright = null;
+
+ /**
+ * The date when the picture was taken as UNIX timestamp.
+ *
+ * @var int
+ */
+ public $date;
+
+ /**
+ * Does the image have a thumbnail?
+ *
+ * @var bool
+ */
+ public $hasThumbnail = false;
+
+ /**
+ * Is the image animated?
+ *
+ * @var bool
+ */
+ public $isAnimated = false;
+
+ /**
+ * Create a new instance of ezcImageAnalyzerData.
+ *
+ * Create a new instance of ezcImageAnalyzerData to be used with
+ * {@link ezcImageAnalyzer} objects.
+ *
+ * @see ezcImageAnalyzer::analyzeImage()
+ * @see ezcImageAnalyzerHandler::analyzeImage()
+ *
+ * @param string $mime {@link ezcImageAnalyzerData::$mime}
+ * @param array $exif {@link ezcImageAnalyzerData::$exif}
+ * @param int $width {@link ezcImageAnalyzerData::$width}
+ * @param int $height {@link ezcImageAnalyzerData::$height}
+ * @param int $size {@link ezcImageAnalyzerData::$size}
+ * @param int $mode {@link ezcImageAnalyzerData::$mode}
+ * @param int $transparencyType {@link ezcImageAnalyzerData::$transparencyType}
+ * @param bool $isColor {@link ezcImageAnalyzerData::$isColor}
+ * @param int $colorCount {@link ezcImageAnalyzerData::$colorCount}
+ * @param string $comment {@link ezcImageAnalyzerData::$comment}
+ * @param array $commentList {@link ezcImageAnalyzerData::$commentList}
+ * @param string $copyright {@link ezcImageAnalyzerData::$copyright}
+ * @param int $date {@link ezcImageAnalyzerData::$date}
+ * @param bool $hasThumbnail {@link ezcImageAnalyzerData::$hasThumbnail}
+ * @param bool $isAnimated {@link ezcImageAnalyzerData::$isAnimated}
+ */
+ public function __construct(
+ $mime = null,
+ $exif = array(),
+ $width = 0,
+ $height = 0,
+ $size = 0,
+ $mode = ezcImageAnalyzerHandler::MODE_TRUECOLOR,
+ $transparencyType = null,
+ $isColor = true,
+ $colorCount = 0,
+ $comment = null,
+ $commentList = array(),
+ $copyright = null,
+ $date = null,
+ $hasThumbnail = false,
+ $isAnimated = false
+ )
+ {
+ $this->mime = $mime;
+ $this->exif = $exif;
+ $this->width = $width;
+ $this->height = $height;
+ $this->size = $size;
+ $this->mode = $mode;
+ $this->transparencyType = $transparencyType;
+ $this->isColor = $isColor;
+ $this->colorCount = $colorCount;
+ $this->comment = $comment;
+ $this->commentList = $commentList;
+ $this->copyright = $copyright;
+ $this->date = $date;
+ $this->hasThumbnail = $hasThumbnail;
+ $this->isAnimated = $isAnimated;
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageAnalysis/tests/analyzer_test.php b/include/ezcomponents/ImageAnalysis/tests/analyzer_test.php
new file mode 100644
index 000000000..2a7012a29
--- /dev/null
+++ b/include/ezcomponents/ImageAnalysis/tests/analyzer_test.php
@@ -0,0 +1,818 @@
+ 'jpeg_exif.jpg',
+ 'noexif_jpeg' => 'jpeg_noexif.jpg',
+ 'exif_tiff' => 'tiff_exif.tiff',
+ 'noexif_tiff' => 'tiff_noexif.tiff',
+ 'animated_gif' => 'gif_animated.gif',
+ 'noanimated_gif' => 'gif_nonanimated.gif',
+ 'noanimated_png' => 'png_nonanimated.png',
+ 'svg' => 'svg.svg',
+ );
+
+ public static function suite()
+ {
+ return new PHPUnit_Framework_TestSuite( "ezcImageAnalysisAnalyzerTest" );
+ }
+
+ protected function setUp()
+ {
+ if ( !ezcBaseFeatures::hasExtensionSupport( 'exif' ) )
+ {
+ $this->markTestSkipped( 'ext/exif is required to run this test.' );
+ }
+
+ $this->basePath = dirname( __FILE__ ) . '/data/';
+ }
+
+ public function testPhpHandlerGetMimeJpegExif()
+ {
+ $file = $this->basePath . $this->testFiles['exif_jpeg'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 'image/jpeg',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for JPEG.'
+ );
+ }
+
+ public function testImagemagickHandlerGetMimeJpegExif()
+ {
+ $file = $this->basePath . $this->testFiles['exif_jpeg'];
+ $analyzer = $this->getAnalyzerImagemagickHandler( $file );
+ $this->assertSame(
+ 'image/jpeg',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for JPEG.'
+ );
+ }
+
+ public function testPhpHandlerGetMimeJpegNoexif()
+ {
+ $file = $this->basePath . $this->testFiles['noexif_jpeg'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 'image/jpeg',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for JPEG.'
+ );
+ }
+
+ public function testImagemagickHandlerGetMimeJpegNoexif()
+ {
+ $file = $this->basePath . $this->testFiles['noexif_jpeg'];
+ $analyzer = $this->getAnalyzerImagemagickHandler( $file );
+ $this->assertSame(
+ 'image/jpeg',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for JPEG.'
+ );
+ }
+
+ public function testPhpHandlerGetMimeTiffExif()
+ {
+ $file = $this->basePath . $this->testFiles['exif_tiff'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 'image/tiff',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for TIFF.'
+ );
+ }
+
+ public function testImagemagickHandlerGetMimeTiffExif()
+ {
+ $file = $this->basePath . $this->testFiles['exif_tiff'];
+ $analyzer = $this->getAnalyzerImagemagickHandler( $file );
+ $this->assertSame(
+ 'image/tiff',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for TIFF.'
+ );
+ }
+
+ public function testPhpHandlerGetMimeTiffNoexif()
+ {
+ $file = $this->basePath . $this->testFiles['noexif_tiff'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 'image/tiff',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for TIFF.'
+ );
+ }
+
+ public function testImagemagickHandlerGetMimeTiffNoexif()
+ {
+ $file = $this->basePath . $this->testFiles['noexif_tiff'];
+ $analyzer = $this->getAnalyzerImagemagickHandler( $file );
+ $this->assertSame(
+ 'image/tiff',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for TIFF.'
+ );
+ }
+
+ public function testPhpHandlerGetMimeGifNonanimated()
+ {
+ $file = $this->basePath . $this->testFiles['noanimated_gif'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 'image/gif',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for GIF.'
+ );
+ }
+
+ public function testImagemagickHandlerGetMimeGifNonanimated()
+ {
+ $file = $this->basePath . $this->testFiles['noanimated_gif'];
+ $analyzer = $this->getAnalyzerImagemagickHandler( $file );
+ $this->assertSame(
+ 'image/gif',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for GIF.'
+ );
+ }
+
+ public function testPhpHandlerGetMimeGifAnimated()
+ {
+ $file = $this->basePath . $this->testFiles['animated_gif'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 'image/gif',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for GIF.'
+ );
+ }
+
+ public function testImagemagickHandlerGetMimeGifAnimated()
+ {
+ $file = $this->basePath . $this->testFiles['animated_gif'];
+ $analyzer = $this->getAnalyzerImagemagickHandler( $file );
+ $this->assertSame(
+ 'image/gif',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for GIF.'
+ );
+ }
+
+ public function testPhpHandlerGetMimePngNonanimated()
+ {
+ $file = $this->basePath . $this->testFiles['noanimated_png'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 'image/png',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for PNG.'
+ );
+ }
+
+ public function testImagemagickHandlerGetMimePngNonanimated()
+ {
+ $file = $this->basePath . $this->testFiles['noanimated_png'];
+ $analyzer = $this->getAnalyzerImagemagickHandler( $file );
+ $this->assertSame(
+ 'image/png',
+ $analyzer->mime,
+ 'MIME-Type was not determined correctly for PNG.'
+ );
+ }
+
+ public function testPhpHandlerJpegExifReportsDetails()
+ {
+ $file = $this->basePath . $this->testFiles['exif_jpeg'];
+ $analyzer = $this->getAnalyzerPhpHandler( $file );
+ $this->assertSame(
+ 399,
+ $analyzer->data->width,
+ 'Find more info about the requirenments on which this design is based on in the +requirenments.txt.
+The main class of the component is the ezcImageManager, which implements a +singleton pattern, because only 1 manager is needed for all actions performed. +The ezcImageManager dispatches the actions performed on images, holds the +ezcImageHandler's (which actually perform the actions) and manages / defines +the ezcImageTransformations which hold conversions and ezcImageFilter's.
+Since 1 filter/conversion can be performed by several ezcImageHandler's, the +manager has a preference list, to determine, which ezcImageHandler to take for +a conversion.
+This interface defines how the abstraction class for an image handler looks +like. ezcImageHandler's utilize a given backend to perform conversion and +filtering (using ezcImageFilter's). A ezcImageHandler knows by hisself, which +filters he implements. The manager will ask it for supported filters to get an +overview, which filters exist.
+To avoid reopening an image file for every operation the ezcImageHandler has +load()/save() methods. An image must be saved before another ezcImageHandler +can perform his actions on it.
+This class implements a storage container for filters to keep them better +consistant than an array could do. Filters are created by the ezcImageManager +or directly through a ezcImageHandler. An ezcImageFilter knows, to which +handler he belongs and dispatches itself to the right handler, when applied.
+ezcImageTransformation's abstract MIME types to allow the easy combination of +conversions and filters which are necessary to get a desired image type. +For conversions only the target MIME type is necessary. Beside that, +ezcImageTransformation's have a reference, which either points to another ezcImageTransformation +or to the special reference __original__ which is the image itself.
+If a ezcImageTransformation references another one, which does not exist, yet, it will +be created, too. This enables one to create a chain of ezcImageTransformation's which +will be processed.
+ezcImageTransformation's will be created on the fly, when the user requests them and not +during startup of the manager. A created ezcImageTransformation will be cached in the +manager for possible later use. Same applies to the filters utilized by the +ezcImageTransformation.
+| Transformation: | +Preview | +
| Reference: | +__original__ | +
| MIME: | +image/JPEG +image/PNG + |
+
| Filters: | +scale 400x400 | +
| Transformation: | +Thumbnail | +
| Reference: | +Preview | +
| MIME: | +image/JPEG +image/PNG + |
+
| Filters: | +scale 100x100 +colorspace grey + |
+
Will scale down the image to 100x100 pixels and convert it to greyscale.
+| Transformation: | +OldPhotos | +
| Reference: | +original | +
| MIME: | +image/JPEG | +
| Filters: | +colorspace grey +border 3 + |
+
Will convert the inserted image to image/JPEG, reduce the colorspace to +greyscale and add a border of 3 pixel.
+Local Variables: +mode: rst +indent-tabs-mode: nil +sentence-end-double-space: t +fill-column: 70 +End: +vim: et syn=rst tw=78 wrap
+
+ * // Prepare settings for ezcImageConverter
+ * // Defines the handlers to utilize and auto conversions.
+ * $settings = new ezcImageConverterSettings(
+ * array(
+ * new ezcImageHandlerSettings( 'GD', 'ezcImageGdHandler' ),
+ * new ezcImageHandlerSettings( 'ImageMagick', 'ezcImageImagemagickHandler' ),
+ * ),
+ * array(
+ * 'image/gif' => 'image/png',
+ * 'image/bmp' => 'image/jpeg',
+ * )
+ * );
+ *
+ * // Create the converter itself.
+ * $converter = new ezcImageConverter( $settings );
+ *
+ * // Define a transformation
+ * $filters = array(
+ * new ezcImageFilter(
+ * 'scaleWidth',
+ * array(
+ * 'width' => 100,
+ * 'direction' => ezcImageGeometryFilters::SCALE_BOTH,
+ * )
+ * ),
+ * new ezcImageFilter(
+ * 'colorspace',
+ * array(
+ * 'space' => ezcImageColorspaceFilters::COLORSPACE_GREY,
+ * )
+ * ),
+ * );
+ *
+ * // Which MIME types the conversion may output
+ * $mimeTypes = array( 'image/jpeg', 'image/png' );
+ *
+ * // Create the transformation inside the manager
+ * $converter->createTransformation( 'thumbnail', $filters, $mimeTypes );
+ *
+ * // Transform an image.
+ * $converter->transform( 'thumbnail', dirname(__FILE__).'/jpeg.jpg', dirname(__FILE__).'/jpeg_thumb.jpg' );
+ *
+ *
+ * It's recommended to create only a single ezcImageConverter instance in your
+ * application to avoid creating multiple instances of it's internal objects.
+ * You can implement a singleton pattern for this, which might look similar to
+ * the following example:
+ *
+ * function getImageConverterInstance()
+ * {
+ * if ( !isset( $GLOBALS['_ezcImageConverterInstance'] ) )
+ * {
+ * // Prepare settings for ezcImageConverter
+ * // Defines the handlers to utilize and auto conversions.
+ * $settings = new ezcImageConverterSettings(
+ * array(
+ * new ezcImageHandlerSettings( 'GD', 'ezcImageGdHandler' ),
+ * new ezcImageHandlerSettings( 'ImageMagick', 'ezcImageImagemagickHandler' ),
+ * ),
+ * array(
+ * 'image/gif' => 'image/png',
+ * 'image/bmp' => 'image/jpeg',
+ * )
+ * );
+ *
+ *
+ * // Create the converter itself.
+ * $converter = new ezcImageConverter( $settings );
+ *
+ * // Define a transformation
+ * $filters = array(
+ * new ezcImageFilter(
+ * 'scale',
+ * array(
+ * 'width' => 100,
+ * 'height' => 300,
+ * 'direction' => ezcImageGeometryFilters::SCALE_BOTH,
+ * )
+ * ),
+ * new ezcImageFilter(
+ * 'colorspace',
+ * array(
+ * 'space' => ezcImageColorspaceFilters::COLORSPACE_SEPIA,
+ * )
+ * ),
+ * new ezcImageFilter(
+ * 'border',
+ * array(
+ * 'width' => 5,
+ * 'color' => array(255, 0, 0),
+ * )
+ * ),
+ * );
+ *
+ * // Which MIME types the conversion may output
+ * $mimeTypes = array( 'image/jpeg', 'image/png' );
+ *
+ * // Create the transformation inside the manager
+ * $converter->createTransformation( 'funny', $filters, $mimeTypes );
+ *
+ * // Assign singleton instance
+ * $GLOBALS['_ezcImageConverterInstance'] = $converter;
+ * }
+ *
+ * // Return singleton instance
+ * return $GLOBALS['_ezcImageConverterInstance'];
+ * }
+ *
+ * // ...
+ * // Somewhere else in the code...
+ * // Transform an image.
+ * getImageConverterInstance()->transform( 'funny', dirname(__FILE__).'/jpeg.jpg', dirname(__FILE__).'/jpeg_singleton.jpg' );
+ *
+ *
+ *
+ * @see ezcImageHandler
+ * @see ezcImageTransformation
+ *
+ * @package ImageConversion
+ * @version 1.3.5
+ * @mainclass
+ */
+class ezcImageConverter
+{
+ /**
+ * Manager settings
+ * Settings basis for all image manipulations.
+ *
+ * @var ezcImageConverterSettings
+ */
+ protected $settings;
+
+ /**
+ * Keeps the handlers used by the converter.
+ *
+ * @var array(ezcImageHandler)
+ */
+ protected $handlers = array();
+
+ /**
+ * Stores transformation registered with this converter.
+ *
+ * @var array
+ */
+ protected $transformations = array();
+
+ /**
+ * Initialize converter with settings object.
+ * The ezcImageConverter can be directly instantiated, but it's
+ * highly recommended to use a manual singleton implementation
+ * to have just 1 instance of a ezcImageConverter per Request.
+ *
+ * ATTENTION: The ezcImageConverter does not support animated
+ * GIFs. Animated GIFs will simply be ignored by all filters and
+ * conversions.
+ *
+ * @param ezcImageConverterSettings $settings Settings for the converter.
+ *
+ * @throws ezcImageHandlerSettingsInvalidException
+ * If handler settings are invalid.
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If a given MIME type is not supported.
+ */
+ public function __construct( ezcImageConverterSettings $settings )
+ {
+ // Initialize handlers
+ foreach ( $settings->handlers as $i => $handlerSettings )
+ {
+ if ( !$handlerSettings instanceof ezcImageHandlerSettings )
+ {
+ throw new ezcImageHandlerSettingsInvalidException();
+ }
+ $handlerClass = $handlerSettings->className;
+ if ( !ezcBaseFeatures::classExists( $handlerClass ) )
+ {
+ throw new ezcImageHandlerNotAvailableException( $handlerClass );
+ }
+ $handler = new $handlerClass( $handlerSettings );
+ $this->handlers[$handlerSettings->referenceName] = $handler;
+ }
+ // Check implicit conversions
+ foreach ( $settings->conversions as $mimeIn => $mimeOut )
+ {
+ if ( !$this->allowsInput( $mimeIn ) )
+ {
+ throw new ezcImageMimeTypeUnsupportedException( $mimeIn, 'input' );
+ }
+ if ( !$this->allowsOutput( $mimeOut ) )
+ {
+ throw new ezcImageMimeTypeUnsupportedException( $mimeOut, 'output' );
+ }
+ }
+ $this->settings = $settings;
+ }
+
+ /**
+ * Create a transformation in the manager.
+ *
+ * Creates a transformation and stores it in the manager. A reference to the
+ * transformation is returned by this method for further manipulation and
+ * to set options on it. The $name can later be used to remove a
+ * transfromation using {@link removeTransformation()} or to execute it
+ * using {@link transform()}. The $filters and $mimeOut parameters specify
+ * the transformation actions as described with {@link
+ * ezcImageTransformation::__construct()}. The $saveOptions are used when
+ * the finally created image is saved and can configure compression and
+ * quality options.
+ *
+ * @param string $name Name for the transformation.
+ * @param array(ezcImageFilter) $filters Filters.
+ * @param array(string) $mimeOut Output MIME types.
+ * @param ezcImageSaveOptions $saveOptions Save options.
+ *
+ * @return ezcImageTransformation
+ *
+ * @throws ezcImageFiltersException
+ * If a given filter does not exist.
+ * @throws ezcImageTransformationAlreadyExists
+ * If a transformation with the given name does already exist.
+ */
+ public function createTransformation( $name, array $filters, array $mimeOut, ezcImageSaveOptions $saveOptions = null )
+ {
+ if ( isset( $this->transformations[$name] ) )
+ {
+ throw new ezcImageTransformationAlreadyExistsException( $name );
+ }
+ $this->transformations[$name] = new ezcImageTransformation( $this, $name, $filters, $mimeOut, $saveOptions );
+ return $this->transformations[$name];
+ }
+
+ /**
+ * Removes a transformation from the manager.
+ *
+ * @param string $name Name of the transformation to remove
+ *
+ * @return ezcImageTransformation The removed transformation
+ *
+ * @throws ezcImageTransformationNotAvailableExeption
+ * If the requested transformation is unknown.
+ */
+ public function removeTransformation( $name )
+ {
+ if ( !isset( $this->transformations[$name] ) )
+ {
+ throw new ezcImageTransformationNotAvailableException( $name );
+ }
+ unset( $this->transformations[$name] );
+ }
+
+ /**
+ * Apply transformation on a file.
+ * This applies the given transformation to the given file.
+ *
+ * @param string $name Name of the transformation to perform
+ * @param string $inFile The file to transform
+ * @param string $outFile The file to save transformed version to
+ *
+ * @throws ezcImageTransformationNotAvailableExeption
+ * If the requested transformation is unknown.
+ * @throws ezcImageTransformationException If an error occurs during the
+ * transformation. The returned exception contains the exception
+ * the problem resulted from in it's public $parent attribute.
+ * @throws ezcBaseFileNotFoundException If the file you are trying to
+ * transform does not exists.
+ * @throws ezcBaseFilePermissionException If the file you are trying to
+ * transform is not readable.
+ */
+ public function transform( $name, $inFile, $outFile )
+ {
+ if ( !isset( $this->transformations[$name] ) )
+ {
+ throw new ezcImageTransformationNotAvailableException( $name );
+ }
+ $this->transformations[$name]->transform( $inFile, $outFile );
+ }
+
+ /**
+ * Returns if a handler is found, supporting the given MIME type for output.
+ *
+ * @param string $mime The MIME type to check for.
+ * @return bool Whether the MIME type is supported.
+ */
+ public function allowsInput( $mime )
+ {
+ foreach ( $this->handlers as $handler )
+ {
+ if ( $handler->allowsInput( $mime ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns if a handler is found, supporting the given MIME type for output.
+ *
+ * @param string $mime The MIME type to check for.
+ * @return bool Whether the MIME type is supported.
+ */
+ public function allowsOutput( $mime )
+ {
+ foreach ( $this->handlers as $handler )
+ {
+ if ( $handler->allowsOutput( $mime ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the MIME type that will be outputted for a given input type.
+ * Checks whether the given input type can be processed. If not, an
+ * exception is thrown. Checks then, if an implicit conversion for that
+ * MIME type is defined. If so, outputs the given output MIME type. In
+ * every other case, just outputs the MIME type given, because no
+ * conversion is implicitly required.
+ *
+ * @param string $mimeIn Input MIME type.
+ * @return string Output MIME type.
+ *
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the input MIME type is not supported.
+ */
+ public function getMimeOut( $mimeIn )
+ {
+ if ( $this->allowsInput( $mimeIn ) === false )
+ {
+ throw new ezcImageMimeTypeUnsupportedException( $mimeIn, 'input' );
+ }
+ if ( isset( $this->settings->conversions[$mimeIn] ) )
+ {
+ return $this->settings->conversions[$mimeIn];
+ }
+ return $mimeIn;
+ }
+
+ /**
+ * Returns if a given filter is available.
+ * Returns either an array of handler names this filter
+ * is available in or false if the filter is not enabled.
+ *
+ * @param string $name Name of the filter to query existance for
+ *
+ * @return mixed Array of handlers on success, otherwise false.
+ */
+ public function hasFilter( $name )
+ {
+ foreach ( $this->handlers as $handler )
+ {
+ if ( $handler->hasFilter( $name ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a list of enabled filters.
+ * Gives you an overview on filters enabled in the manager.
+ * Format is:
+ *
+ * array(
+ * '',
+ * );
+ *
+ *
+ * @return array(string)
+ */
+ public function getFilterNames()
+ {
+ $filters = array();
+ foreach ( $this->handlers as $handler )
+ {
+ $filters = array_merge( $filters, $handler->getFilterNames() );
+ }
+ return array_unique( $filters );
+ }
+
+ /**
+ * Apply a single filter to an image.
+ * Applies just a single filter to an image. Optionally you can select
+ * a handler yourself, which is not recommended, but possible. If the
+ * specific handler does not have that filter, ImageConverter will try
+ * to fall back on another handler.
+ *
+ * @param ezcImageFilter $filter Filter object to apply.
+ * @param string $inFile Name of the input file.
+ * @param string $outFile Name of the output file.
+ * @param string $handlerName
+ * To choose a specific handler, this is the reference named passed
+ * to {@link ezcImageHandlerSettings}.
+ * @return void
+ *
+ *
+ * @throws ezcImageHandlerNotAvailableException
+ * If fitting handler is not available.
+ * @throws ezcImageFilterNotAvailableException
+ * If filter is not available.
+ * @throws ezcImageFileNameInvalidException
+ * If an invalid character (", ', $) is found in the file name.
+ */
+ public function applyFilter( ezcImageFilter $filter, $inFile, $outFile, $handlerName = null )
+ {
+ $handlerObj = false;
+ // Do we have an explicit handler given?
+ if ( $handlerName !== null )
+ {
+ if ( !isset( $this->handlers[$handlerName] ) )
+ {
+ throw new ezcImageHandlerNotAvailableException( $handlerName );
+ }
+ if ( $this->handlers[$handlerName]->hasFilter( $filter->name ) === true )
+ {
+ $handlerObj = $this->handlers[$handlerName];
+ }
+ }
+ // Either no handler explicitly given or try to fall back.
+ if ( $handlerObj === false )
+ {
+ foreach ( $this->handlers as $regHandler )
+ {
+ if ( $regHandler->hasFilter( $filter->name ) )
+ {
+ $handlerObj = $regHandler;
+ break;
+ }
+ }
+ }
+ // No handler found to apply filter with.
+ if ( $handlerObj === false )
+ {
+ throw new ezcImageFilterNotAvailableException( $filter->name );
+ }
+ $imgRef = $handlerObj->load( $inFile );
+ $handlerObj->applyFilter( $imgRef, $filter );
+ $handlerObj->save( $imgRef, $outFile );
+ }
+
+ /**
+ * Returns a handler object for direct use.
+ * Returns the handler with the highest priority, that supports the given
+ * filter, MIME input type and MIME output type. All parameters are
+ * optional, if none is specified, the highest prioritized handler is
+ * returned.
+ *
+ * If no handler is found, that supports the criteria named, an exception
+ * of type {@link ezcImageHandlerNotAvailableException} will be thrown.
+ *
+ * @param string $filterName Name of the filter to search for.
+ * @param string $mimeIn Input MIME type.
+ * @param string $mimeOut Output MIME type.
+ *
+ * @return ezcImageHandler
+ *
+ * @throws ezcImageHandlerNotAvailableException
+ * If a handler for the given specification could not be found.
+ */
+ public function getHandler( $filterName = null, $mimeIn = null, $mimeOut = null )
+ {
+ foreach ( $this->handlers as $handler )
+ {
+ if ( ( !isset( $filterName ) || $handler->hasFilter( $filterName ) )
+ && ( !isset( $mimeIn ) || $handler->allowsInput( $mimeIn ) )
+ && ( !isset( $mimeOut ) || $handler->allowsOutput( $mimeOut ) )
+ )
+ {
+ return $handler;
+ }
+ }
+ throw new ezcImageHandlerNotAvailableException( 'unknown' );
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/exception.php b/include/ezcomponents/ImageConversion/src/exceptions/exception.php
new file mode 100644
index 000000000..5b201b5c7
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/exception.php
@@ -0,0 +1,20 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/file_name_invalid.php b/include/ezcomponents/ImageConversion/src/exceptions/file_name_invalid.php
new file mode 100644
index 000000000..832e6aebb
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/file_name_invalid.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/file_not_processable.php b/include/ezcomponents/ImageConversion/src/exceptions/file_not_processable.php
new file mode 100644
index 000000000..a5dcfc586
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/file_not_processable.php
@@ -0,0 +1,37 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/filter_failed.php b/include/ezcomponents/ImageConversion/src/exceptions/filter_failed.php
new file mode 100644
index 000000000..a8376d939
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/filter_failed.php
@@ -0,0 +1,37 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/filter_not_available.php b/include/ezcomponents/ImageConversion/src/exceptions/filter_not_available.php
new file mode 100644
index 000000000..7742aaa8f
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/filter_not_available.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/handler_not_available.php b/include/ezcomponents/ImageConversion/src/exceptions/handler_not_available.php
new file mode 100644
index 000000000..6fcf02ccc
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/handler_not_available.php
@@ -0,0 +1,37 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/handler_settings_invalid.php b/include/ezcomponents/ImageConversion/src/exceptions/handler_settings_invalid.php
new file mode 100644
index 000000000..3b11d3b98
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/handler_settings_invalid.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/invalid_filter_parameter.php b/include/ezcomponents/ImageConversion/src/exceptions/invalid_filter_parameter.php
new file mode 100644
index 000000000..d5fb7193a
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/invalid_filter_parameter.php
@@ -0,0 +1,40 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/invalid_reference.php b/include/ezcomponents/ImageConversion/src/exceptions/invalid_reference.php
new file mode 100644
index 000000000..701ca535e
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/invalid_reference.php
@@ -0,0 +1,37 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/mime_type_unsupported.php b/include/ezcomponents/ImageConversion/src/exceptions/mime_type_unsupported.php
new file mode 100644
index 000000000..ea91ed9b6
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/mime_type_unsupported.php
@@ -0,0 +1,32 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/missing_filter_parameter.php b/include/ezcomponents/ImageConversion/src/exceptions/missing_filter_parameter.php
new file mode 100644
index 000000000..54863cdc0
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/missing_filter_parameter.php
@@ -0,0 +1,32 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/transformation.php b/include/ezcomponents/ImageConversion/src/exceptions/transformation.php
new file mode 100644
index 000000000..9dae896d0
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/transformation.php
@@ -0,0 +1,55 @@
+parent = $e;
+ $message = $e->getMessage();
+ parent::__construct( "Transformation failed. '{$message}'." );
+ }
+
+}
+?>
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/transformation_already_exists.php b/include/ezcomponents/ImageConversion/src/exceptions/transformation_already_exists.php
new file mode 100644
index 000000000..459605a2c
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/transformation_already_exists.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/exceptions/transformation_not_available.php b/include/ezcomponents/ImageConversion/src/exceptions/transformation_not_available.php
new file mode 100644
index 000000000..ee2c91612
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/exceptions/transformation_not_available.php
@@ -0,0 +1,31 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/handlers/gd.php b/include/ezcomponents/ImageConversion/src/handlers/gd.php
new file mode 100644
index 000000000..c3694eede
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/handlers/gd.php
@@ -0,0 +1,953 @@
+ 0' );
+ }
+ if ( !is_int( $height ) || $height < 1 )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int > 0' );
+ }
+
+ $resource = $this->getActiveResource();
+ $oldDim = array( 'x' => imagesx( $resource ), 'y' => imagesy( $resource ) );
+
+ $widthRatio = $width / $oldDim['x'];
+ $heighRatio = $height / $oldDim['y'];
+
+ $ratio = min( $widthRatio, $heighRatio );
+
+ switch ( $direction )
+ {
+ case self::SCALE_DOWN:
+ $ratio = $ratio < 1 ? $ratio : 1;
+ break;
+ case self::SCALE_UP:
+ $ratio = $ratio > 1 ? $ratio : 1;
+ break;
+ case self::SCALE_BOTH:
+ break;
+ default:
+ throw new ezcBaseValueException( 'direction', $direction, 'self::SCALE_BOTH, self::SCALE_UP, self::SCALE_DOWN' );
+ break;
+ }
+ $this->performScale( round( $oldDim['x'] * $ratio ), round( $oldDim['y'] * $ratio ) );
+ }
+
+ /**
+ * Scale after width filter.
+ * Scales the image to a give width, measured in pixel. Scales the height
+ * automatically while keeping the ratio. The direction dictates, if an
+ * image may only be scaled {@link self::SCALE_UP}, {@link self::SCALE_DOWN}
+ * or if the scale may work in {@link self::SCALE_BOTH} directions.
+ *
+ * ATTENTION: Using this filter method directly results in the filter being
+ * applied to the image which is internally marked as "active" (most
+ * commonly this is the last recently loaded one). It is highly recommended
+ * to apply filters through the {@link ezcImageGdHandler::applyFilter()} method,
+ * which enables you to specify the image a filter is applied to.
+ *
+ * @param int $width Scale to width
+ * @param int $direction Scale to which direction
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function scaleWidth( $width, $direction )
+ {
+ if ( !is_int( $width ) || $width < 1 )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int > 0' );
+ }
+
+ $resource = $this->getActiveResource();
+ $oldDim = array(
+ 'x' => imagesx( $resource ),
+ 'y' => imagesy( $resource ),
+ );
+ switch ( $direction )
+ {
+ case self::SCALE_BOTH:
+ $newDim = array(
+ 'x' => $width,
+ 'y' => $width / $oldDim['x'] * $oldDim['y']
+ );
+ break;
+ case self::SCALE_UP:
+ $newDim = array(
+ 'x' => max( $width, $oldDim['x'] ),
+ 'y' => $width > $oldDim['x'] ? round( $width / $oldDim['x'] * $oldDim['y'] ) : $oldDim['y'],
+ );
+ break;
+ case self::SCALE_DOWN:
+ $newDim = array(
+ 'x' => min( $width, $oldDim['x'] ),
+ 'y' => $width < $oldDim['x'] ? round( $width / $oldDim['x'] * $oldDim['y'] ) : $oldDim['y'],
+ );
+ break;
+ default:
+ throw new ezcBaseValueException( 'direction', $direction, 'self::SCALE_BOTH, self::SCALE_UP, self::SCALE_DOWN' );
+ break;
+ }
+ $this->performScale( $newDim["x"], $newDim["y"] );
+ }
+
+ /**
+ * Scale after height filter.
+ * Scales the image to a give height, measured in pixel. Scales the width
+ * automatically while keeping the ratio. The direction dictates, if an
+ * image may only be scaled {@link self::SCALE_UP}, {@link self::SCALE_DOWN}
+ * or if the scale may work in {@link self::SCALE_BOTH} directions.
+ *
+ * ATTENTION: Using this filter method directly results in the filter being
+ * applied to the image which is internally marked as "active" (most
+ * commonly this is the last recently loaded one). It is highly recommended
+ * to apply filters through the {@link ezcImageGdHandler::applyFilter()} method,
+ * which enables you to specify the image a filter is applied to.
+ *
+ * @param int $height Scale to height
+ * @param int $direction Scale to which direction
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function scaleHeight( $height, $direction )
+ {
+ if ( !is_int( $height ) || $height < 1 )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int > 0' );
+ }
+
+ $resource = $this->getActiveResource();
+ $oldDim = array(
+ 'x' => imagesx( $resource ),
+ 'y' => imagesy( $resource ),
+ );
+ switch ( $direction )
+ {
+ case self::SCALE_BOTH:
+ $newDim = array(
+ 'x' => $height / $oldDim['y'] * $oldDim['x'],
+ 'y' => $height,
+ );
+ break;
+ case self::SCALE_UP:
+ $newDim = array(
+ 'x' => $height > $oldDim['y'] ? round( $height / $oldDim['y'] * $oldDim['x'] ) : $oldDim['x'],
+ 'y' => max( $height, $oldDim['y'] ),
+ );
+ break;
+ case self::SCALE_DOWN:
+ $newDim = array(
+ 'x' => $height < $oldDim['y'] ? round( $height / $oldDim['y'] * $oldDim['x'] ) : $oldDim['x'],
+ 'y' => min( $height, $oldDim['y'] ),
+ );
+ break;
+ default:
+ throw new ezcBaseValueException( 'direction', $direction, 'self::SCALE_BOTH, self::SCALE_UP, self::SCALE_DOWN' );
+ break;
+ }
+ $this->performScale( $newDim["x"], $newDim["y"] );
+ }
+
+ /**
+ * Scale percent measures filter.
+ * Scale an image to a given percentage value size.
+ *
+ * ATTENTION: Using this filter method directly results in the filter being
+ * applied to the image which is internally marked as "active" (most
+ * commonly this is the last recently loaded one). It is highly recommended
+ * to apply filters through the {@link ezcImageGdHandler::applyFilter()} method,
+ * which enables you to specify the image a filter is applied to.
+ *
+ * @param int $width Scale to width
+ * @param int $height Scale to height
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function scalePercent( $width, $height )
+ {
+ if ( !is_int( $height ) || $height < 1 )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int > 0' );
+ }
+ if ( !is_int( $width ) || $width < 1 )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int > 0' );
+ }
+
+ $resource = $this->getActiveResource();
+ $this->performScale( round( imagesx( $resource ) * $width / 100 ), round( imagesy( $resource ) * $height / 100 ) );
+ }
+
+ /**
+ * Scale exact filter.
+ * Scale the image to a fixed given pixel size, no matter to which
+ * direction.
+ *
+ * ATTENTION: Using this filter method directly results in the filter being
+ * applied to the image which is internally marked as "active" (most
+ * commonly this is the last recently loaded one). It is highly recommended
+ * to apply filters through the {@link ezcImageGdHandler::applyFilter()} method,
+ * which enables you to specify the image a filter is applied to.
+ *
+ * @param int $width Scale to width
+ * @param int $height Scale to height
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function scaleExact( $width, $height )
+ {
+ if ( !is_int( $height ) || $height < 1 )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int > 0' );
+ }
+ if ( !is_int( $width ) || $width < 1 )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int > 0' );
+ }
+ $this->performScale( $width, $height );
+ }
+
+ /**
+ * Crop filter.
+ * Crop an image to a given size. This takes cartesian coordinates of a
+ * rect area to crop from the image. The cropped area will replace the old
+ * image resource (not the input image immediately, if you use the
+ * {@link ezcImageConverter}). Coordinates are given as integer values and
+ * are measured from the top left corner.
+ *
+ * ATTENTION: Using this filter method directly results in the filter being
+ * applied to the image which is internally marked as "active" (most
+ * commonly this is the last recently loaded one). It is highly recommended
+ * to apply filters through the {@link ezcImageGdHandler::applyFilter()} method,
+ * which enables you to specify the image a filter is applied to.
+ *
+ * @param int $x X offset of the cropping area.
+ * @param int $y Y offset of the cropping area.
+ * @param int $width Width of cropping area.
+ * @param int $height Height of cropping area.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function crop( $x, $y, $width, $height )
+ {
+ if ( !is_int( $x ) )
+ {
+ throw new ezcBaseValueException( 'x', $x, 'int' );
+ }
+ if ( !is_int( $y ) )
+ {
+ throw new ezcBaseValueException( 'y', $y, 'int' );
+ }
+ if ( !is_int( $height ) )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int' );
+ }
+ if ( !is_int( $width ) )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int' );
+ }
+
+ $oldResource = $this->getActiveResource();
+
+ $sourceWidth = imagesx( $oldResource );
+ $sourceHeight = imagesy( $oldResource );
+
+ $x = ( $x >= 0 ) ? $x : $sourceWidth + $x;
+ $y = ( $y >= 0 ) ? $y : $sourceHeight + $y;
+
+ $x = abs( min( $x, $x + $width ) );
+ $y = abs( min( $y, $y + $height ) );
+
+ $width = abs( $width );
+ $height = abs( $height );
+
+ if ( $x + $width > $sourceWidth )
+ {
+ $width = $sourceWidth - $x;
+ }
+ if ( $y + $height > $sourceHeight )
+ {
+ $height = $sourceHeight - $y;
+ }
+
+ $this->performCrop( $x, $y, $width, $height );
+
+ }
+
+ /**
+ * Colorspace filter.
+ * Transform the colorspace of the picture. The following colorspaces are
+ * supported:
+ *
+ * - {@link self::COLORSPACE_GREY} - 255 grey colors
+ * - {@link self::COLORSPACE_SEPIA} - Sepia colors
+ * - {@link self::COLORSPACE_MONOCHROME} - 2 colors black and white
+ *
+ * ATTENTION: Using this filter method directly results in the filter being
+ * applied to the image which is internally marked as "active" (most
+ * commonly this is the last recently loaded one). It is highly recommended
+ * to apply filters through the {@link ezcImageGdHandler::applyFilter()} method,
+ * which enables you to specify the image a filter is applied to.
+ *
+ * @param int $space Colorspace, one of self::COLORSPACE_* constants.
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcBaseValueException
+ * If the parameter submitted as the colorspace was not within the
+ * self::COLORSPACE_* constants.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ */
+ public function colorspace( $space )
+ {
+ switch ( $space )
+ {
+ case self::COLORSPACE_GREY:
+ $this->luminanceColorScale( array( 1.0, 1.0, 1.0 ) );
+ break;
+ case self::COLORSPACE_MONOCHROME:
+ $this->thresholdColorScale(
+ array(
+ 127 => array( 0, 0, 0 ),
+ 255 => array( 255, 255, 255 ),
+ )
+ );
+ break;
+ return;
+ case self::COLORSPACE_SEPIA:
+ $this->luminanceColorScale( array( 1.0, 0.89, 0.74 ) );
+ break;
+ default:
+ throw new ezcBaseValueException( 'space', $space, 'self::COLORSPACE_GREY, self::COLORSPACE_SEPIA, self::COLORSPACE_MONOCHROME' );
+ break;
+ }
+
+ }
+
+ /**
+ * Watermark filter.
+ * Places a watermark on the image. The file to use as the watermark image
+ * is given as $image. The $posX, $posY and $size values are given in
+ * percent, related to the destination image. A $size value of 10 will make
+ * the watermark appear in 10% of the destination image size.
+ * $posX = $posY = 10 will make the watermark appear in the top left corner
+ * of the destination image, 10% of its size away from its borders. If
+ * $size is ommitted, the watermark image will not be resized.
+ *
+ * @param string $image The image file to use as the watermark
+ * @param int $posX X position in the destination image in percent.
+ * @param int $posY Y position in the destination image in percent.
+ * @param int|bool $size Percentage size of the watermark, false for none.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function watermarkPercent( $image, $posX, $posY, $size = false )
+ {
+ if ( !is_string( $image ) || !file_exists( $image ) || !is_readable( $image ) )
+ {
+ throw new ezcBaseValueException( 'image', $image, 'string, path to an image file' );
+ }
+ if ( !is_int( $posX ) || $posX < 0 || $posX > 100 )
+ {
+ throw new ezcBaseValueException( 'posX', $posX, 'int percentage value' );
+ }
+ if ( !is_int( $posY ) || $posY < 0 || $posY > 100 )
+ {
+ throw new ezcBaseValueException( 'posY', $posY, 'int percentage value' );
+ }
+ if ( !is_bool( $size ) && ( !is_int( $size ) || $size < 0 || $size > 100 ) )
+ {
+ throw new ezcBaseValueException( 'size', $size, 'int percentage value / bool' );
+ }
+
+ $imgWidth = imagesx( $this->getActiveResource() );
+ $imgHeight = imagesy( $this->getActiveResource() );
+
+ $watermarkWidth = false;
+ $watermarkHeight = false;
+ if ( $size !== false )
+ {
+ $watermarkWidth = (int) round( $imgWidth * $size / 100 );
+ $watermarkHeight = (int) round( $imgHeight * $size / 100 );
+ }
+
+ $watermarkPosX = (int) round( $imgWidth * $posX / 100 );
+ $watermarkPosY = (int) round( $imgHeight * $posY / 100 );
+
+ $this->watermarkAbsolute( $image, $watermarkPosX, $watermarkPosY, $watermarkWidth, $watermarkHeight );
+ }
+
+ /**
+ * Watermark filter.
+ * Places a watermark on the image. The file to use as the watermark image
+ * is given as $image. The $posX, $posY and $size values are given in
+ * pixel. The watermark appear at $posX, $posY in the destination image
+ * with a size of $size pixel. If $size is ommitted, the watermark image
+ * will not be resized.
+ *
+ * @param string $image The image file to use as the watermark
+ * @param int $posX X position in the destination image in pixel.
+ * @param int $posY Y position in the destination image in pixel.
+ * @param int|bool $width Pixel size of the watermark, false to keep size.
+ * @param int|bool $height Pixel size of the watermark, false to keep size.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ * @throws ezcImageFileNotProcessableException
+ * If the given watermark image could not be loaded.
+ */
+ public function watermarkAbsolute( $image, $posX, $posY, $width = false, $height = false )
+ {
+ if ( !is_string( $image ) || !file_exists( $image ) || !is_readable( $image ) )
+ {
+ throw new ezcBaseValueException( 'image', $image, 'string, path to an image file' );
+ }
+ if ( !is_int( $posX ) )
+ {
+ throw new ezcBaseValueException( 'posX', $posX, 'int' );
+ }
+ if ( !is_int( $posY ) )
+ {
+ throw new ezcBaseValueException( 'posY', $posY, 'int' );
+ }
+ if ( !is_int( $width ) && !is_bool( $width ) )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int/bool' );
+ }
+ if ( !is_int( $height ) && !is_bool( $height ) )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int/bool' );
+ }
+
+ // Backup original image reference
+ $originalRef = $this->getActiveReference();
+
+ $originalWidth = imagesx( $this->getActiveResource() );
+ $originalHeight = imagesy( $this->getActiveResource() );
+
+ $watermarkRef = $this->load( $image );
+ if ( $width !== false && $height !== false && ( $originalWidth !== $width || $originalHeight !== $height ) )
+ {
+ $this->scale( $width, $height, ezcImageGeometryFilters::SCALE_BOTH );
+ }
+
+ // Negative offsets
+ $posX = ( $posX >= 0 ) ? $posX : $originalWidth + $posX;
+ $posY = ( $posY >= 0 ) ? $posY : $originalHeight + $posY;
+
+ imagecopy(
+ $this->getReferenceData( $originalRef, "resource" ), // resource $dst_im
+ $this->getReferenceData( $watermarkRef, "resource" ), // resource $src_im
+ $posX, // int $dst_x
+ $posY, // int $dst_y
+ 0, // int $src_x
+ 0, // int $src_y
+ imagesx( $this->getReferenceData( $watermarkRef, "resource" ) ), // int $src_w
+ imagesy( $this->getReferenceData( $watermarkRef, "resource" ) ) // int $src_h
+ );
+
+ $this->close( $watermarkRef );
+
+ // Restore original image reference
+ $this->setActiveReference( $originalRef );
+ }
+
+ /**
+ * Creates a thumbnail, and crops parts of the given image to fit the range best.
+ * This filter creates a thumbnail of the given image. The image is scaled
+ * down, keeping the original ratio and keeping the image larger as the
+ * given range, if necessary. Overhead for the target range is cropped from
+ * both sides equally.
+ *
+ * If you are looking for a filter that just resizes your image to
+ * thumbnail size, you should consider the {@link
+ * ezcImageGdHandler::scale()} filter.
+ *
+ * @param int $width Width of the thumbnail.
+ * @param int $height Height of the thumbnail.
+ */
+ public function croppedThumbnail( $width, $height )
+ {
+ if ( !is_int( $width ) || $width < 1 )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int > 0' );
+ }
+ if ( !is_int( $height ) || $height < 1 )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int > 0' );
+ }
+ $resource = $this->getActiveResource();
+ $data[0] = imagesx( $resource );
+ $data[1] = imagesy( $resource );
+
+ $scaleRatio = max( $width / $data[0], $height / $data[1] );
+ $scaleWidth = round( $data[0] * $scaleRatio );
+ $scaleHeight = round( $data[1] * $scaleRatio );
+
+ $cropOffsetX = ( $scaleWidth > $width ) ? round( ( $scaleWidth - $width ) / 2 ) : 0;
+ $cropOffsetY = ( $scaleHeight > $height ) ? round( ( $scaleHeight - $height ) / 2 ) : 0;
+
+ $this->performScale( $scaleWidth, $scaleHeight );
+ $this->performCrop( $cropOffsetX, $cropOffsetY, $width, $height );
+ }
+
+ /**
+ * Creates a thumbnail, and fills up the image to fit the given range.
+ * This filter creates a thumbnail of the given image. The image is scaled
+ * down, keeping the original ratio and scaling the image smaller as the
+ * given range, if necessary. Overhead for the target range is filled with the given
+ * color on both sides equally.
+ *
+ * The color is defined by the following array format (integer values 0-255):
+ *
+ *
+ * array(
+ * 0 => ,
+ * 1 => ,
+ * 2 => ,
+ * );
+ *
+ *
+ * If you are looking for a filter that just resizes your image to
+ * thumbnail size, you should consider the {@link
+ * ezcImageGdHandler::scale()} filter.
+ *
+ * @param int $width Width of the thumbnail.
+ * @param int $height Height of the thumbnail.
+ * @param array $color Fill color.
+ *
+ * @return void
+ */
+ public function filledThumbnail( $width, $height, $color = array() )
+ {
+ $i = 0;
+ foreach ( $color as $id => $colorVal )
+ {
+ if ( $i++ > 2 )
+ {
+ break;
+ }
+ if ( !is_int( $colorVal ) || $colorVal < 0 || $colorVal > 255 )
+ {
+ throw new ezcBaseValueException( "color[$id]", $color[$id], 'int > 0 and < 256' );
+ }
+ }
+
+ // Sanity checks for $width and $height performed by scale() method.
+ $this->scale( $width, $height, ezcImageGeometryFilters::SCALE_BOTH );
+
+ $oldResource = $this->getActiveResource();
+
+ $realWidth = imagesx( $oldResource );
+ $realHeight = imagesy( $oldResource );
+ $xOffset = ( $width > $realWidth ) ? round( ( $width - $realWidth ) / 2 ) : 0;
+ $yOffset = ( $height > $realHeight ) ? round( ( $height - $realHeight ) / 2 ) : 0;
+
+ $newResource = imagecreatetruecolor( $width, $height );
+ $bgColor = $this->getColor( $newResource, $color[0], $color[1], $color[2] );
+ if ( imagefill( $newResource, 0, 0, $bgColor ) === false )
+ {
+ throw new ezcImageFilterFailedException( "filledThumbnail", "Color fill failed." );
+ }
+
+ imagecopy(
+ $newResource,
+ $oldResource,
+ $xOffset,
+ $yOffset,
+ 0,
+ 0,
+ $realWidth,
+ $realHeight
+ );
+
+ $this->setActiveResource( $newResource );
+ imagedestroy( $oldResource );
+ }
+
+ // private
+
+ /**
+ * Retrieve luminance value for a specific pixel.
+ *
+ * @param resource(GD) $resource Image resource
+ * @param int $x Pixel x coordinate.
+ * @param int $y Pixel y coordinate.
+ * @return float Luminance value.
+ */
+ private function getLuminanceAt( $resource, $x, $y )
+ {
+ $currentColor = imagecolorat( $resource, $x, $y );
+ $rgbValues = array(
+ 'r' => ( $currentColor >> 16 ) & 0xff,
+ 'g' => ( $currentColor >> 8 ) & 0xff,
+ 'b' => $currentColor & 0xff,
+ );
+ return $rgbValues['r'] * 0.299 + $rgbValues['g'] * 0.587 + $rgbValues['b'] * 0.114;
+ }
+
+ /**
+ * Scale colors by threshold values.
+ * Thresholds are defined by the following array structures:
+ *
+ *
+ * array(
+ * => array(
+ * 0 => ,
+ * 1 => ,
+ * 2 => ,
+ * ),
+ * )
+ *
+ *
+ * @param array $thresholds
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ *
+ * @todo Optimization as described here: http://lists.ez.no/pipermail/components/2005-November/000566.html
+ */
+ protected function thresholdColorScale( $thresholds )
+ {
+ $resource = $this->getActiveResource();
+ $dimensions = array( 'x' => imagesx( $resource ), 'y' => imagesy( $resource ) );
+
+ // Check for GIFs and convert them to work properly here.
+ if ( !imageistruecolor( $resource ) )
+ {
+ $resource = $this->paletteToTruecolor( $resource, $dimensions );
+ }
+
+ foreach ( $thresholds as $threshold => $colors )
+ {
+ $thresholds[$threshold] = array_merge(
+ $colors,
+ array( 'color' => $this->getColor( $resource, $colors[0], $colors[1], $colors[2] ) )
+ );
+ }
+ // Default
+ if ( !isset( $thresholds[255] ) )
+ {
+ $thresholds[255] = end( $thresholds );
+ reset( $thresholds );
+ }
+
+ $colorCache = array();
+
+ for ( $x = 0; $x < $dimensions['x']; $x++ )
+ {
+ for ( $y = 0; $y < $dimensions['y']; $y++ )
+ {
+ $luminance = $this->getLuminanceAt( $resource, $x, $y );
+ $color = end( $thresholds );
+ foreach ( $thresholds as $threshold => $colorValues )
+ {
+ if ( $luminance <= $threshold )
+ {
+ $color = $colorValues;
+ break;
+ }
+ }
+ imagesetpixel( $resource, $x, $y, $color['color'] );
+ }
+ }
+
+ $this->setActiveResource( $resource );
+ }
+
+ /**
+ * Perform luminance color scale.
+ *
+ * @param array $scale Array of RGB values (numeric index).
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed
+ *
+ * @todo Optimization as described here: http://lists.ez.no/pipermail/components/2005-November/000566.html
+ */
+ protected function luminanceColorScale( $scale )
+ {
+ $resource = $this->getActiveResource();
+ $dimensions = array( 'x' => imagesx( $resource ), 'y' => imagesy( $resource ) );
+
+ // Check for GIFs and convert them to work properly here.
+ if ( !imageistruecolor( $resource ) )
+ {
+ $resource = $this->paletteToTruecolor( $resource, $dimensions );
+ }
+
+ for ( $x = 0; $x < $dimensions['x']; $x++ )
+ {
+ for ( $y = 0; $y < $dimensions['y']; $y++ )
+ {
+ $luminance = $this->getLuminanceAt( $resource, $x, $y );
+ $newRgbValues = array(
+ 'r' => $luminance * $scale[0],
+ 'g' => $luminance * $scale[1],
+ 'b' => $luminance * $scale[2],
+ );
+ $color = $this->getColor( $resource, $newRgbValues['r'], $newRgbValues['g'], $newRgbValues['b'] );
+ imagesetpixel( $resource, $x, $y, $color );
+ }
+ }
+ $this->setActiveResource( $resource );
+ }
+
+ /**
+ * Convert a palette based image resource to a true color one.
+ * Takes a GD resource that does not represent a true color image and
+ * converts it to a true color based resource. Do not forget, to replace
+ * the actual resource in the handler, if you use this ,method!
+ *
+ * @param resource(GD) $resource The image resource to convert
+ * @param array(string=>int) $dimensions X and Y dimensions.
+ * @return resource(GD) The converted resource.
+ */
+ protected function paletteToTruecolor( $resource, $dimensions )
+ {
+ $newResource = imagecreatetruecolor( $dimensions['x'], $dimensions['y'] );
+ imagecopy( $newResource, $resource, 0, 0, 0, 0, $dimensions['x'], $dimensions['y'] );
+ imagedestroy( $resource );
+ return $newResource;
+ }
+
+ /**
+ * Common color determination method.
+ * Returns a color identifier for an RGB value. Avoids problems with palette images.
+ *
+ * @param reource(GD) $resource The image resource to get a color for.
+ * @param int $r Red value.
+ * @param int $g Green value.
+ * @param int $b Blue value.
+ *
+ * @return int The color identifier.
+ *
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ */
+ protected function getColor( $resource, $r, $g, $b )
+ {
+ if ( ( $res = imagecolorexact( $resource, $r, $g, $b ) ) !== -1 )
+ {
+ return $res;
+ }
+ if ( ( $res = imagecolorallocate( $resource, $r, $g, $b ) ) !== -1 )
+ {
+ return $res;
+ }
+ if ( ( $res = imagecolorclosest( $resource, $r, $g, $b ) ) !== -1 )
+ {
+ return $res;
+ }
+ throw new ezcImageFilterFailedException( 'allocatecolor', "Color allocation failed for color r: '{$r}', g: '{$g}', b: '{$b}'." );
+ }
+
+ /**
+ * General scaling method to perform actual scale to new dimensions.
+ *
+ * @param int $width Width.
+ * @param int $height Height.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException.
+ * If the operation performed by the the filter failed.
+ */
+ protected function performScale( $width, $height )
+ {
+ $oldResource = $this->getActiveResource();
+ if ( imageistruecolor( $oldResource ) )
+ {
+ $newResource = imagecreatetruecolor( $width, $height );
+ }
+ else
+ {
+ $newResource = imagecreate( $width, $height );
+ }
+
+ // Save transparency, if image has it
+ $bgColor = imagecolorallocatealpha( $newResource, 255, 255, 255, 127 );
+ imagealphablending( $newResource, true );
+ imagesavealpha( $newResource, true );
+ imagefill( $newResource, 1, 1, $bgColor );
+
+ $res = imagecopyresampled(
+ $newResource,
+ $oldResource,
+ 0, 0, 0, 0,
+ $width,
+ $height,
+ imagesx( $this->getActiveResource() ),
+ imagesy( $this->getActiveResource() )
+ );
+ if ( $res === false )
+ {
+ throw new ezcImageFilterFailedException( 'scale', 'Resampling of image failed.' );
+ }
+ imagedestroy( $oldResource );
+ $this->setActiveResource( $newResource );
+ }
+
+ /**
+ * General method to perform a crop operation.
+ *
+ * @param int $x
+ * @param int $y
+ * @param int $width
+ * @param int $height
+ * @return void
+ */
+ private function performCrop( $x, $y, $width, $height )
+ {
+ $oldResource = $this->getActiveResource();
+ if ( imageistruecolor( $oldResource ) )
+ {
+ $newResource = imagecreatetruecolor( $width, $height );
+ }
+ else
+ {
+ $newResource = imagecreate( $width, $height );
+ }
+
+ // Save transparency, if image has it
+ $bgColor = imagecolorallocatealpha( $newResource, 255, 255, 255, 127 );
+ imagealphablending( $newResource, true );
+ imagesavealpha( $newResource, true );
+ imagefill( $newResource, 1, 1, $bgColor );
+
+ $res = imagecopyresampled(
+ $newResource, // destination resource
+ $oldResource, // source resource
+ 0, // destination x coord
+ 0, // destination y coord
+ $x, // source x coord
+ $y, // source y coord
+ $width, // destination width
+ $height, // destination height
+ $width, // source witdh
+ $height // source height
+ );
+ if ( $res === false )
+ {
+ throw new ezcImageFilterFailedException( 'crop', 'Resampling of image failed.' );
+ }
+ imagedestroy( $oldResource );
+ $this->setActiveResource( $newResource );
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageConversion/src/handlers/gd_base.php b/include/ezcomponents/ImageConversion/src/handlers/gd_base.php
new file mode 100644
index 000000000..0a8abfe4b
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/handlers/gd_base.php
@@ -0,0 +1,297 @@
+determineTypes();
+ parent::__construct( $settings );
+ }
+
+ /**
+ * Load an image file.
+ * Loads an image file and returns a reference to it.
+ *
+ * @param string $file File to load.
+ * @param string $mime The MIME type of the file.
+ *
+ * @return string Reference to the file in this handler.
+ *
+ * @see ezcImageAnalyzer
+ *
+ * @throws ezcBaseFileNotFoundException
+ * If the given file does not exist.
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the type of the given file is not recognized
+ * @throws ezcImageFileNotProcessableException
+ * If the given file is not processable using this handler.
+ * @throws ezcImageFileNameInvalidException
+ * If an invalid character (", ', $) is found in the file name.
+ */
+ public function load( $file, $mime = null )
+ {
+ $this->checkFileName( $file );
+ $ref = $this->loadCommon( $file, isset( $mime ) ? $mime : null );
+ $loadFunction = $this->getLoadFunction( $this->getReferenceData( $ref, 'mime' ) );
+ if ( !ezcBaseFeatures::hasFunction( $loadFunction ) || ( $handle = @$loadFunction( $file ) ) === '' )
+ {
+ throw new ezcImageFileNotProcessableException( $file, "File could not be opened using $loadFunction." );
+ }
+ $this->setReferenceData( $ref, $handle, 'resource' );
+ return $ref;
+ }
+
+ /**
+ * Save an image file.
+ * Saves a given open file. Can optionally save to a new file name.
+ *
+ * @see ezcImageHandler::load()
+ *
+ * @param string $image File reference created through load().
+ * @param string $newFile Filename to save the image to.
+ * @param string $mime New MIME type, if differs from initial one.
+ * @param ezcImageSaveOptions $options Save options.
+ * @return void
+ *
+ * @throws ezcImageFileNotProcessableException
+ * If the given file could not be saved with the given MIME type.
+ * @throws ezcBaseFilePermissionException
+ * If the desired file exists and is not writeable.
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the desired MIME type is not recognized
+ * @throws ezcImageFileNameInvalidException
+ * If an invalid character (", ', $) is found in the file name.
+ */
+ public function save( $image, $newFile = null, $mime = null, ezcImageSaveOptions $options = null )
+ {
+ $options = ( $options === null ) ? new ezcImageSaveOptions() : $options;
+
+ if ( $newFile !== null )
+ {
+ $this->checkFileName( $newFile );
+ }
+
+ // Check is transparency must be converted
+ if ( $this->needsTransparencyConversion( $this->getReferenceData( $image, 'mime' ), $mime ) && $options->transparencyReplacementColor !== null )
+ {
+ $this->replaceTransparency( $image, $options->transparencyReplacementColor );
+ }
+
+ $this->saveCommon( $image, isset( $newFile ) ? $newFile : null, isset( $mime ) ? $mime : null );
+ $saveFunction = $this->getSaveFunction( $this->getReferenceData( $image, 'mime' ) );
+
+ $saveParams = array(
+ $this->getReferenceData( $image, 'resource' ),
+ $this->getReferenceData( $image, 'file' ),
+ );
+ switch ( $saveFunction )
+ {
+ case "imagejpeg":
+ if ( $options->quality !== null )
+ {
+ $saveParams[] = $options->quality;
+ }
+ break;
+ case "imagepng":
+ if ( $options->compression !== null )
+ {
+ $saveParams[] = $options->compression;
+ }
+ break;
+ }
+
+ if ( !ezcBaseFeatures::hasFunction( $saveFunction ) ||
+ call_user_func_array( $saveFunction, $saveParams ) === false )
+ {
+ throw new ezcImageFileNotProcessableException( $file, "Unable to save file '{$file}' of type '{$mime}'." );
+ }
+ }
+
+ /**
+ * Replaces a transparent background with the given color.
+ *
+ * This method is used to replace the transparent background of an image
+ * with an opaque color when converting from a transparency supporting MIME
+ * type (e.g. image/png) to a MIME type that does not support transparency.
+ *
+ * The color
+ *
+ * @param mixed $image
+ * @param mixed $color
+ * @return void
+ */
+ protected function replaceTransparency( $image, array $color )
+ {
+ $oldResource = $this->getReferenceData( $image, 'resource' );
+ $width = imagesx( $oldResource );
+ $height = imagesy( $oldResource );
+ if ( imageistruecolor( $oldResource ) )
+ {
+ $newResource = imagecreatetruecolor( $width, $height );
+ }
+ else
+ {
+ $newResource = imagecreate( $width, $height );
+ }
+
+ $bgColor = imagecolorallocate( $newResource, $color[0], $color[1], $color[2] );
+ imagefill( $newResource, 0, 0, $bgColor );
+
+ // $res = imagecopyresampled(
+ $res = imagecopyresampled(
+ $newResource, // destination resource
+ $oldResource, // source resource
+ 0, // destination x coord
+ 0, // destination y coord
+ 0, // source x coord
+ 0, // source y coord
+ $width, // destination width
+ $height, // destination height
+ $width, // source witdh
+ $height // source height
+ );
+ if ( $res === false )
+ {
+ throw new ezcImageFilterFailedException( 'crop', 'Resampling of image failed.' );
+ }
+ imagedestroy( $oldResource );
+ $this->setReferenceData( $image, $newResource, 'resource' );
+ }
+
+ /**
+ * Close the file referenced by $image.
+ * Frees the image reference. You should call close() before.
+ *
+ * @see ezcImageHandler::load()
+ * @see ezcImageHandler::save()
+ *
+ * @param string $image The image reference.
+ * @return void
+ */
+ public function close( $image )
+ {
+ $res = $this->getReferenceData( $image, 'resource' );
+ if ( is_resource( $res ) )
+ {
+ imagedestroy( $res );
+ }
+ $this->closeCommon( $image );
+ }
+
+ /**
+ * Determine, the image types the available GD extension is able to process.
+ *
+ * @return void
+ */
+ private function determineTypes()
+ {
+ $possibleTypes = array(
+ IMG_GIF => 'image/gif',
+ IMG_JPG => 'image/jpeg',
+ IMG_PNG => 'image/png',
+ IMG_WBMP => 'image/wbmp',
+ IMG_XPM => 'image/xpm',
+ );
+ $imageTypes = imagetypes();
+ foreach ( $possibleTypes as $bit => $mime )
+ {
+ if ( $imageTypes & $bit )
+ {
+ $this->inputTypes[] = $mime;
+ $this->outputTypes[] = $mime;
+ }
+ }
+ }
+
+ /**
+ * Generate imagecreatefrom* function out of a MIME type.
+ *
+ * @param string $mime MIME type in format "image/
+ * array(
+ * 0 => ,
+ * 1 => ,
+ * 2 => ,
+ * );
+ *
+ *
+ * ATTENTION: Using this filter method directly results in the filter being
+ * applied to the image which is internally marked as "active" (most
+ * commonly this is the last recently loaded one). It is highly recommended
+ * to apply filters through the {@link ezcImageImagemagickHandler::applyFilter()}
+ * method, which enables you to specify the image a filter is applied to.
+ *
+ * @param int $width Width of the border.
+ * @param array(int) $color Color.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * No loaded file could be found or an error destroyed a loaded reference.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function border( $width, array $color )
+ {
+ if ( !is_int( $width ) )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int' );
+ }
+ $colorString = $this->colorArrayToString( $color );
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-bordercolor',
+ $colorString
+ );
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-border',
+ $width
+ );
+ }
+
+ /**
+ * Watermark filter.
+ * Places a watermark on the image. The file to use as the watermark image
+ * is given as $image. The $posX, $posY and $size values are given in
+ * percent, related to the destination image. A $size value of 10 will make
+ * the watermark appear in 10% of the destination image size.
+ * $posX = $posY = 10 will make the watermark appear in the top left corner
+ * of the destination image, 10% of its size away from its borders. If
+ * $size is ommitted, the watermark image will not be resized.
+ *
+ * @param string $image The image file to use as the watermark
+ * @param int $posX X position in the destination image in percent.
+ * @param int $posY Y position in the destination image in percent.
+ * @param int|bool $size Percentage size of the watermark, false for none.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function watermarkPercent( $image, $posX, $posY, $size = false )
+ {
+ if ( !is_string( $image ) || !file_exists( $image ) || !is_readable( $image ) )
+ {
+ throw new ezcBaseValueException( 'image', $image, 'string, path to an image file' );
+ }
+ if ( !is_int( $posX ) || $posX < 0 || $posX > 100 )
+ {
+ throw new ezcBaseValueException( 'posX', $posX, 'int percentage value' );
+ }
+ if ( !is_int( $posY ) || $posY < 0 || $posY > 100 )
+ {
+ throw new ezcBaseValueException( 'posY', $posY, 'int percentage value' );
+ }
+ if ( !is_bool( $size ) && ( !is_int( $size ) || $size < 0 || $size > 100 ) )
+ {
+ throw new ezcBaseValueException( 'size', $size, 'int percentage value / bool' );
+ }
+
+ $data = getimagesize( $this->getReferenceData( $this->getActiveReference(), "resource" ) );
+
+ $originalWidth = $data[0];
+ $originalHeight = $data[1];
+
+ $watermarkWidth = false;
+ $watermarkHeight = false;
+
+ if ( $size !== false )
+ {
+ $watermarkWidth = (int) round( $originalWidth * $size / 100 );
+ $watermarkHeight = (int) round( $originalHeight * $size / 100 );
+ }
+
+ $watermarkPosX = (int) round( $originalWidth * $posX / 100 );
+ $watermarkPosY = (int) round( $originalHeight * $posY / 100 );
+
+ $this->watermarkAbsolute( $image, $watermarkPosX, $watermarkPosY, $watermarkWidth, $watermarkHeight );
+
+ }
+
+ /**
+ * Watermark filter.
+ * Places a watermark on the image. The file to use as the watermark image
+ * is given as $image. The $posX, $posY and $size values are given in
+ * pixel. The watermark appear at $posX, $posY in the destination image
+ * with a size of $size pixel. If $size is ommitted, the watermark image
+ * will not be resized.
+ *
+ * @param string $image The image file to use as the watermark
+ * @param int $posX X position in the destination image in pixel.
+ * @param int $posY Y position in the destination image in pixel.
+ * @param int|bool $width Pixel size of the watermark, false to keep size.
+ * @param int|bool $height Pixel size of the watermark, false to keep size.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ public function watermarkAbsolute( $image, $posX, $posY, $width = false, $height = false )
+ {
+ if ( !is_string( $image ) || !file_exists( $image ) || !is_readable( $image ) )
+ {
+ throw new ezcBaseValueException( 'image', $image, 'string, path to an image file' );
+ }
+ if ( !is_int( $posX ) )
+ {
+ throw new ezcBaseValueException( 'posX', $posX, 'int' );
+ }
+ if ( !is_int( $posY ) )
+ {
+ throw new ezcBaseValueException( 'posY', $posY, 'int' );
+ }
+ if ( !is_int( $width ) && !is_bool( $width ) )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int/bool' );
+ }
+ if ( !is_int( $height ) && !is_bool( $height ) )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int/bool' );
+ }
+
+ $data = getimagesize( $this->getActiveResource() );
+
+ // Negative offsets
+ $posX = ( $posX >= 0 ) ? $posX : $data[0] + $posX;
+ $posY = ( $posY >= 0 ) ? $posY : $data[1] + $posY;
+
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-composite',
+ ''
+ );
+
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-geometry',
+ ( $width !== false ? $width : "" ) . ( $height !== false ? "x$height" : "" ) . "+$posX+$posY"
+ );
+
+ $this->addCompositeImage( $this->getActiveReference(), $image );
+ }
+
+ /**
+ * Creates a thumbnail, and crops parts of the given image to fit the range best.
+ * This filter creates a thumbnail of the given image. The image is scaled
+ * down, keeping the original ratio and keeping the image larger as the
+ * given range, if necessary. Overhead for the target range is cropped from
+ * both sides equally.
+ *
+ * If you are looking for a filter that just resizes your image to
+ * thumbnail size, you should consider the {@link
+ * ezcImageImagemagickHandler::scale()} filter.
+ *
+ * @param int $width Width of the thumbnail.
+ * @param int $height Height of the thumbnail.
+ */
+ public function croppedThumbnail( $width, $height )
+ {
+ if ( !is_int( $width ) || $width < 1 )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int > 0' );
+ }
+ if ( !is_int( $height ) || $height < 1 )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int > 0' );
+ }
+ $data = getimagesize( $this->getReferenceData( $this->getActiveReference(), "resource" ) );
+
+ $scaleRatio = max( $width / $data[0], $height / $data[1] );
+ $scaleWidth = round( $data[0] * $scaleRatio );
+ $scaleHeight = round( $data[1] * $scaleRatio );
+
+ $cropOffsetX = ( $scaleWidth > $width ) ? "+" . round( ( $scaleWidth - $width ) / 2 ) : "+0";
+ $cropOffsetY = ( $scaleHeight > $height ) ? "+" . round( ( $scaleHeight - $height ) / 2 ) : "+0";
+
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-resize',
+ $scaleWidth . "x" . $scaleHeight
+ );
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-crop',
+ $width . "x" . $height . $cropOffsetX . $cropOffsetY . "!"
+ );
+ }
+
+ /**
+ * Creates a thumbnail, and fills up the image to fit the given range.
+ * This filter creates a thumbnail of the given image. The image is scaled
+ * down, keeping the original ratio and scaling the image smaller as the
+ * given range, if necessary. Overhead for the target range is filled with the given
+ * color on both sides equally.
+ *
+ * The color is defined by the following array format (integer values 0-255):
+ *
+ *
+ * array(
+ * 0 => ,
+ * 1 => ,
+ * 2 => ,
+ * );
+ *
+ *
+ * If you are looking for a filter that just resizes your image to
+ * thumbnail size, you should consider the {@link
+ * ezcImageImagemagickHandler::scale()} filter.
+ *
+ * @param int $width Width of the thumbnail.
+ * @param int $height Height of the thumbnail.
+ * @param array $color Fill color.
+ * @return void
+ */
+ public function filledThumbnail( $width, $height, $color = array() )
+ {
+ if ( !is_int( $width ) || $width < 1 )
+ {
+ throw new ezcBaseValueException( 'width', $width, 'int > 0' );
+ }
+ if ( !is_int( $height ) || $height < 1 )
+ {
+ throw new ezcBaseValueException( 'height', $height, 'int > 0' );
+ }
+ $data = getimagesize( $this->getReferenceData( $this->getActiveReference(), "resource" ) );
+
+ $scaleRatio = min( $width / $data[0], $height / $data[1] );
+ $scaleWidth = round( $data[0] * $scaleRatio );
+ $scaleHeight = round( $data[1] * $scaleRatio );
+
+ $cropOffsetX = ( $scaleWidth < $width ) ? "-" . round( ( $width - $scaleWidth ) / 2 ) : "+0";
+ $cropOffsetY = ( $scaleHeight < $height ) ? "-" . round( ( $height - $scaleHeight ) / 2 ) : "+0";
+
+ $colorString = '#';
+ $i = 0;
+ foreach ( $color as $id => $colorVal )
+ {
+ if ( $i++ > 2 )
+ {
+ break;
+ }
+ if ( !is_int( $colorVal ) || $colorVal < 0 || $colorVal > 255 )
+ {
+ throw new ezcBaseValueException( "color[$id]", $color[$id], 'int > 0 and < 256' );
+ }
+ $colorString .= sprintf( '%02x', $colorVal );
+ }
+
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-resize',
+ $width . "x" . $height
+ );
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-crop',
+ $width . "x" . $height . $cropOffsetX . $cropOffsetY . "!"
+ );
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-background',
+ $colorString
+ );
+ $this->addFilterOption(
+ $this->getActiveReference(),
+ '-flatten'
+ );
+ }
+
+ /**
+ * Returns the ImageMagick direction modifier for a direction constant.
+ * ImageMagick supports the following modifiers to determine if an
+ * image should be scaled up only, down only or in both directions:
+ *
+ *
+ * SCALE_UP: >
+ * SCALE_DOWN: <
+ *
+ *
+ * This method returns the correct modifier for the internal direction
+ * constants.
+ *
+ * @param int $direction One of ezcImageGeometryFilters::SCALE_*
+ * @return string The correct modifier.
+ *
+ * @throws ezcBaseValueException
+ * If a submitted parameter was out of range or type.
+ */
+ protected function getDirectionModifier( $direction )
+ {
+ $dirMod = '';
+ switch ( $direction )
+ {
+ case self::SCALE_DOWN:
+ $dirMod = '>';
+ break;
+ case self::SCALE_UP:
+ $dirMod = '<';
+ break;
+ case self::SCALE_BOTH:
+ $dirMod = '';
+ break;
+ default:
+ throw new ezcBaseValueException( 'direction', $direction, 'self::SCALE_BOTH, self::SCALE_UP, self::SCALE_DOWN' );
+ break;
+ }
+ return $dirMod;
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageConversion/src/handlers/imagemagick_base.php b/include/ezcomponents/ImageConversion/src/handlers/imagemagick_base.php
new file mode 100644
index 000000000..dd3081f06
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/handlers/imagemagick_base.php
@@ -0,0 +1,471 @@
+string)
+ */
+ private $tagMap = array();
+
+ /**
+ * Filter options per reference.
+ *
+ * @var array(string=>array)
+ */
+ private $filterOptions = array();
+
+ /**
+ * Composite image setting per reference.
+ *
+ * @var array(string=>bool)
+ */
+ private $compositeImages = array();
+
+ /**
+ * Create a new image handler.
+ * Creates an image handler. This should never be done directly,
+ * but only through the manager for configuration reasons. One can
+ * get a direct reference through manager afterwards.
+ *
+ * This handler has an option 'binary' available, which allows you to
+ * explicitly set the path to your ImageMagicks "convert" binary (this
+ * may be necessary on Windows, since there may be an obscure "convert.exe"
+ * in the $PATH variable available, which has nothing to do with
+ * ImageMagick).
+ *
+ * @throws ezcImageHandlerNotAvailableException
+ * If the ImageMagick binary is not found.
+ *
+ * @param ezcImageHandlerSettings $settings Settings for the handler.
+ */
+ public function __construct( ezcImageHandlerSettings $settings )
+ {
+ // Check for ImageMagick
+ $this->checkImageMagick( $settings );
+ $this->determineTypes();
+ parent::__construct( $settings );
+ }
+
+ /**
+ * Load an image file.
+ * Loads an image file and returns a reference to it.
+ *
+ * @param string $file File to load.
+ * @param string $mime The MIME type of the file.
+ *
+ * @return string Reference to the file in this handler.
+ *
+ * @see ezcImageAnalyzer
+ *
+ * @throws ezcBaseFileNotFoundException
+ * If the desired file does not exist.
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the desired file has a not recognized type.
+ * @throws ezcImageFileNameInvalidException
+ * If an invalid character (", ', $) is found in the file name.
+ */
+ public function load( $file, $mime = null )
+ {
+ $this->checkFileName( $file );
+ $ref = $this->loadCommon( $file, $mime );
+
+ // Atomic file operation
+ $fileTmp = tempnam( dirname( $file ) . DIRECTORY_SEPARATOR, '.' . basename( $file ) );
+ copy( $file, $fileTmp );
+
+ $this->setReferenceData( $ref, $fileTmp, 'resource' );
+ return $ref;
+ }
+
+ /**
+ * Save an image file.
+ * Saves a given open file. Can optionally save to a new file name.
+ *
+ * @see ezcImageHandler::load()
+ *
+ * @param string $image File reference created through load().
+ * @param string $newFile Filename to save the image to.
+ * @param string $mime New MIME type, if differs from initial one.
+ * @param ezcImageSaveOptions $options Save options.
+ * @return void
+ *
+ * @throws ezcBaseFilePermissionException
+ * If the desired file exists and is not writeable.
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the desired MIME type is not recognized.
+ * @throws ezcImageFileNameInvalidException
+ * If an invalid character (", ', $) is found in the file name.
+ */
+ public function save( $image, $newFile = null, $mime = null, ezcImageSaveOptions $options = null )
+ {
+ if ( $options === null )
+ {
+ $options = new ezcImageSaveOptions();
+ }
+
+ if ( $newFile !== null )
+ {
+ $this->checkFileName( $newFile );
+ }
+
+ // Check is transparency must be converted
+ if ( $this->needsTransparencyConversion( $this->getReferenceData( $image, 'mime' ), $mime ) && $options->transparencyReplacementColor !== null )
+ {
+ $this->addFilterOption( $image, '-background', $this->colorArrayToString( $options->transparencyReplacementColor ) );
+ $this->addFilterOption( $image, '-flatten' );
+ }
+
+ $this->saveCommon( $image, $newFile, $mime );
+
+ switch ( $this->getReferenceData( $image, 'mime' ) )
+ {
+ case "image/jpeg":
+ if ( $options->quality !== null )
+ {
+ $this->addFilterOption( $image, "-quality", $options->quality );
+ }
+ break;
+ case "image/png":
+ if ( $options->compression !== null )
+ {
+ // ImageMagick uses qualtiy options here and incorporates filter options
+ $this->addFilterOption( $image, "-quality", $options->compression * 10 );
+ }
+ break;
+ }
+
+ // Prepare ImageMagick command
+ // Here we need a work around, because older ImageMagick versions do not
+ // support this option order
+
+ if ( isset( $this->compositeImages[$image] ) )
+ {
+ $command = $this->binary . ' ' .
+ ( isset( $this->filterOptions[$image] ) ? implode( ' ', $this->filterOptions[$image] ) : '' ) . ' ' .
+ escapeshellarg( $this->getReferenceData( $image, 'resource' ) ) . ' ' .
+ implode( ' ', $this->compositeImages[$image] ) . ' ' .
+ escapeshellarg( $this->tagMap[$this->getReferenceData( $image, 'mime' )] . ':' . $this->getReferenceData( $image, 'resource' ) );
+ }
+ else
+ {
+ $command = $this->binary . ' ' .
+ escapeshellarg( $this->getReferenceData( $image, 'resource' ) ) . ' ' .
+ ( isset( $this->filterOptions[$image] ) ? implode( ' ', $this->filterOptions[$image] ) : '' ) . ' ' .
+ escapeshellarg( $this->tagMap[$this->getReferenceData( $image, 'mime' )] . ':' . $this->getReferenceData( $image, 'resource' ) );
+ }
+
+
+ // Prepare to run ImageMagick command
+ $descriptors = array(
+ array( 'pipe', 'r' ),
+ array( 'pipe', 'w' ),
+ array( 'pipe', 'w' ),
+ );
+
+ // Open ImageMagick process
+ $imageProcess = proc_open( $command, $descriptors, $pipes );
+ // Close STDIN pipe
+ fclose( $pipes[0] );
+
+ $errorString = '';
+ $outputString = '';
+ // Read STDERR
+ do
+ {
+ $outputString .= rtrim( fgets( $pipes[1], 1024 ), "\n" );
+ $errorString .= rtrim( fgets( $pipes[2], 1024 ), "\n" );
+ } while ( !feof( $pipes[2] ) );
+
+ // Wait for process to terminate and store return value
+ $status = proc_get_status( $imageProcess );
+ while ( $status['running'] === true )
+ {
+ // Sleep 1/100 second to wait for convert to exit
+ usleep( 10000 );
+ $status = proc_get_status( $imageProcess );
+ }
+ $return = proc_close( $imageProcess );
+
+ // Process potential errors
+ if ( $status['exitcode'] != 0 || strlen( $errorString ) > 0 )
+ {
+ // If this code is reached we have a bug in this component or in ImageMagick itself.
+ throw new Exception(
+ "The command '{$command}' resulted in an error ({$status['exitcode']})): '{$errorString}'. Output: '{$outputString}'"
+ );
+ }
+ // Finish atomic file operation
+ copy( $this->getReferenceData( $image, 'resource' ), $this->getReferenceData( $image, 'file' ) );
+ }
+
+ /**
+ * Returns a string representation of the given color array.
+ *
+ * ImageConversion uses arrays to represent color values, in the format:
+ *
+ * array(
+ * 255,
+ * 0,
+ * 0,
+ * )
+ *
+ * This array represents the color red.
+ *
+ * This method takes such a color array and converts it into a string
+ * representation usable by the convert binary. For the above examle it
+ * would be '#FF0000'.
+ *
+ * @param array $color
+ * @return void
+ *
+ * @throws ezcBaseValueException
+ * if one of the color values in the array is invalid (not integer,
+ * smaller than 0 or larger than 255).
+ */
+ protected function colorArrayToString( array $color )
+ {
+ $colorString = '#';
+ $i = 0;
+ foreach ( $color as $id => $colorVal )
+ {
+ if ( $i++ > 2 )
+ {
+ break;
+ }
+ if ( !is_int( $colorVal ) || $colorVal < 0 || $colorVal > 255 )
+ {
+ throw new ezcBaseValueException( "color[$id]", $color[$id], 'int > 0 and < 256' );
+ }
+ $colorString .= sprintf( '%02x', $colorVal );
+ }
+ return $colorString;
+ }
+
+ /**
+ * Close the file referenced by $image.
+ * Frees the image reference. You should call close() before.
+ *
+ * @see ezcImageHandler::load()
+ * @see ezcImageHandler::save()
+ * @param string $image The image reference.
+ */
+ public function close( $image )
+ {
+ unlink( $this->getReferenceData( $image, 'resource' ) );
+ $this->setReferenceData( $image, false, 'resource' );
+ $this->closeCommon( $image );
+ }
+
+ /**
+ * Add a filter option to a given reference
+ *
+ * @param string $reference The reference to add a filter for.
+ * @param string $name The option name.
+ * @param string $parameter The option parameter.
+ * @return void
+ */
+ protected function addFilterOption( $reference, $name, $parameter = null )
+ {
+ $this->filterOptions[$reference][] = $name . ( $parameter !== null ? ' ' . escapeshellarg( $parameter ) : '' );
+ }
+
+ /**
+ * Add an image to composite with the given reference.
+ *
+ * @param string $reference The reference to add an image to
+ * @param string $file The file to composite with the image.
+ * @return void
+ */
+ protected function addCompositeImage( $reference, $file )
+ {
+ $this->compositeImages[$reference][] = $file;
+ }
+
+ /**
+ * Determines the supported input/output types supported by handler.
+ * Set's various attributes to reflect the MIME types this handler is
+ * capable to process.
+ *
+ * @return void
+ *
+ * @apichange Faulty MIME type "image/svg" will be removed and replaced by
+ * correct MIME type image/svg+xml.
+ */
+ private function determineTypes()
+ {
+ $tagMap = array(
+ 'application/pcl' => 'PCL',
+ 'application/pdf' => 'PDF',
+ 'application/postscript' => 'PS',
+ 'application/vnd.palm' => 'PDB',
+ 'application/x-icb' => 'ICB',
+ 'application/x-mif' => 'MIFF',
+ 'image/bmp' => 'BMP3',
+ 'image/dcx' => 'DCX',
+ 'image/g3fax' => 'G3',
+ 'image/gif' => 'GIF',
+ 'image/jng' => 'JNG',
+ 'image/jpeg' => 'JPG',
+ 'image/pbm' => 'PBM',
+ 'image/pcd' => 'PCD',
+ 'image/pict' => 'PCT',
+ 'image/pjpeg' => 'PJPEG',
+ 'image/png' => 'PNG',
+ 'image/ras' => 'RAS',
+ 'image/sgi' => 'SGI',
+ 'image/svg+xml' => 'SVG',
+ // Left over for BC reasons
+ 'image/svg' => 'SVG',
+ 'image/tga' => 'TGA',
+ 'image/tiff' => 'TIF',
+ 'image/vda' => 'VDA',
+ 'image/vnd.wap.wbmp' => 'WBMP',
+ 'image/vst' => 'VST',
+ 'image/x-fits' => 'FITS',
+ 'image/x-otb' => 'OTB',
+ 'image/x-palm' => 'PALM',
+ 'image/x-pcx' => 'PCX',
+ 'image/x-pgm' => 'PGM',
+ 'image/psd' => 'PSD',
+ 'image/x-ppm' => 'PPM',
+ 'image/x-ptiff' => 'PTIF',
+ 'image/x-viff' => 'VIFF',
+ 'image/x-xbitmap' => 'XPM',
+ 'image/x-xv' => 'P7',
+ 'image/xpm' => 'PICON',
+ 'image/xwd' => 'XWD',
+ 'text/plain' => 'TXT',
+ 'video/mng' => 'MNG',
+ 'video/mpeg' => 'MPEG',
+ 'video/mpeg2' => 'M2V',
+ );
+ $types = array_keys( $tagMap );
+ $this->inputTypes = $types;
+ $this->outputTypes = $types;
+ $this->tagMap = $tagMap;
+ }
+
+ /**
+ * Checks for ImageMagick on the system.
+ *
+ * @param ezcImageHandlerSettings $settings The settings object of the current handler instance.
+ * @return void
+ *
+ * @throws ezcImageHandlerNotAvailableException
+ * If the ImageMagick binary is not found.
+ */
+ private function checkImageMagick( ezcImageHandlerSettings $settings )
+ {
+ if ( !isset( $settings->options['binary'] ) )
+ {
+ // Try to use basic binary names only, if not provided (standard case
+ // on Unix, binary should be in the $PATH, so is accessable).
+ switch ( PHP_OS )
+ {
+ case 'Linux':
+ case 'Unix':
+ case 'FreeBSD':
+ case 'MacOS':
+ case 'Darwin':
+ $this->binary = 'convert';
+ break;
+ case 'Windows':
+ case 'WINNT':
+ case 'WIN32':
+ $this->binary = 'convert.exe';
+ break;
+ default:
+ throw new ezcImageHandlerNotAvailableException( 'ezcImageImagemagickHandler', "System '" . PHP_OS . "' not supported by handler 'ezcImageImagemagickHandler'." );
+ break;
+ }
+ }
+ else
+ {
+ $this->binary = $settings->options['binary'];
+ }
+
+ // Prepare to run ImageMagick command
+ $descriptors = array(
+ array( 'pipe', 'r' ),
+ array( 'pipe', 'w' ),
+ array( 'pipe', 'w' ),
+ );
+
+ // Open ImageMagick process
+ $imageProcess = proc_open( $this->binary, $descriptors, $pipes );
+
+ // Close STDIN pipe
+ fclose( $pipes[0] );
+
+ $outputString = '';
+ // Read STDOUT
+ do
+ {
+ $outputString .= rtrim( fgets( $pipes[1], 1024 ), "\n" );
+ } while ( !feof( $pipes[1] ) );
+
+ $errorString = '';
+ // Read STDERR
+ do
+ {
+ $errorString .= rtrim( fgets( $pipes[2], 1024 ), "\n" );
+ } while ( !feof( $pipes[2] ) );
+
+ // Wait for process to terminate and store return value
+ $return = proc_close( $imageProcess );
+
+ // Process potential errors
+ if ( $return != 0 || strlen( $errorString ) > 0 || strpos( $outputString, 'ImageMagick' ) === false )
+ {
+ throw new ezcImageHandlerNotAvailableException( 'ezcImageImagemagickHandler', 'ImageMagick not installed or not available in PATH variable.' );
+ }
+ }
+
+ /**
+ * Creates default settings for the handler and returns it.
+ * The reference name will be set to 'ImageMagick'.
+ *
+ * @return ezcImageHandlerSettings
+ */
+ static public function defaultSettings()
+ {
+ return new ezcImageHandlerSettings( 'ImageMagick', 'ezcImageImagemagickHandler' );
+ }
+}
+
+?>
diff --git a/include/ezcomponents/ImageConversion/src/interfaces/colorspace.php b/include/ezcomponents/ImageConversion/src/interfaces/colorspace.php
new file mode 100644
index 000000000..efe751436
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/interfaces/colorspace.php
@@ -0,0 +1,69 @@
+
diff --git a/include/ezcomponents/ImageConversion/src/interfaces/effect.php b/include/ezcomponents/ImageConversion/src/interfaces/effect.php
new file mode 100644
index 000000000..c0d917d97
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/interfaces/effect.php
@@ -0,0 +1,90 @@
+
+ * array(
+ * 0 =>
+ * array(
+ * 0 => ,
+ * 1 => ,
+ * ...
+ * )
+ *
+ *
+ * @return array(string)
+ */
+ abstract public function getFilterNames();
+
+ /**
+ * Applies a filter to a given image.
+ *
+ * @internal This method is the main one, which will dispatch the
+ * filter action to the specific function of the backend.
+ *
+ * @see ezcImageHandler::load()
+ * @see ezcImageHandler::save()
+ *
+ * @param string $image Image reference to apply the filter on.
+ * @param ezcImageFilter $filter Contains which filter operation to apply.
+ * @return void
+ *
+ * @throws ezcImageFilterNotAvailableException
+ * If the desired filter does not exist.
+ * @throws ezcImageMissingFilterParameterException
+ * If a parameter for the filter is missing.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a parameter was not within the expected range.
+ */
+ abstract public function applyFilter( $image, ezcImageFilter $filter );
+
+ /**
+ * Converts an image to another MIME type.
+ *
+ * Use {@link ezcImageHandler::allowsOutput()} to determine,
+ * if the output MIME type is supported by this handler!
+ *
+ * @see ezcImageHandler::load()
+ * @see ezcImageHandler::save()
+ *
+ * @param string $image Image reference to convert.
+ * @param string $mime MIME type to convert to.
+ * @return void
+ *
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the given MIME type is not supported by the filter.
+ */
+ abstract public function convert( $image, $mime );
+}
+?>
diff --git a/include/ezcomponents/ImageConversion/src/interfaces/methodcall_handler.php b/include/ezcomponents/ImageConversion/src/interfaces/methodcall_handler.php
new file mode 100644
index 000000000..bcee4bfc5
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/interfaces/methodcall_handler.php
@@ -0,0 +1,573 @@
+ array(
+ * 'file' =>
+ * array(
+ * 0 => ,
+ * 1 => ,
+ * ...
+ * )
+ *
+ *
+ * @return array(string)
+ */
+ public function getFilterNames()
+ {
+ if ( !isset( $this->filterNameCache ) || !is_array( $this->filterNameCache || sizeof( $this->filterNameCache ) === 0 ) )
+ {
+ $this->filterNameCache = array();
+ $excludeMethods = array(
+ '__construct',
+ '__destruct',
+ '__get',
+ '__set',
+ '__call',
+ 'allowsInput',
+ 'allowsOutput',
+ 'hasFilter',
+ 'getFilterNames',
+ 'applyFilter',
+ 'convert',
+ 'load',
+ 'save',
+ 'close',
+ 'defaultSettings',
+ );
+
+ $refClass = new ReflectionClass( get_class( $this ) );
+ foreach ( $refClass->getMethods() as $method )
+ {
+ if ( $method->isPublic() && !in_array( $method->getName(), $excludeMethods ) )
+ {
+ $this->filterNameCache[] = $method->getName();
+ }
+ }
+ }
+ return $this->filterNameCache;
+ }
+
+ /**
+ * Applies a filter to a given image.
+ *
+ * @internal This method is the main one, which will dispatch the
+ * filter action to the specific function of the backend.
+ *
+ * @see ezcImageMethodcallHandler::load()
+ * @see ezcImageMethodcallHandler::save()
+ *
+ * @param string $image Image reference to apply the filter on.
+ * @param ezcImageFilter $filter Contains which filter operation to apply.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ * @throws ezcImageInvalidReferenceException
+ * No loaded file could be found or an error destroyed a loaded reference.
+ * @throws ezcImageFilterNotAvailableException
+ * If the desired filter does not exist.
+ * @throws ezcImageFiltersMissingFilterParameterException
+ * If a parameter for the filter is missing.
+ * @throws ezcImageFilterFailedException
+ * If the operation performed by the the filter failed.
+ * @throws ezcBaseValueException
+ * If a parameter was not within the expected range.
+ */
+ public function applyFilter( $image, ezcImageFilter $filter )
+ {
+ if ( !$this->hasFilter( $filter->name ) )
+ {
+ throw new ezcImageFilterNotAvailableException( $filter->name );
+ }
+ $reflectClass = new ReflectionClass( get_class( $this ) );
+ $reflectParameters = $reflectClass->getMethod( $filter->name )->getParameters();
+ $parameters = array();
+ foreach ( $reflectParameters as $id => $parameter )
+ {
+ $paramName = $parameter->getName();
+ if ( isset( $filter->options[$paramName] ) )
+ {
+ $parameters[] = $filter->options[$paramName];
+ }
+ else if ( $parameter->isOptional() === false )
+ {
+ throw new ezcImageMissingFilterParameterException( $filter->name, $paramName );
+ }
+ }
+ // Backup last active reference
+ $oldRef = $this->getActiveReference();
+ // Perform actual filtering on given image
+ $this->setActiveReference( $image );
+ call_user_func_array( array( $this, $filter->name ), $parameters );
+ // Restore last active reference
+ $this->setActiveReference( $oldRef );
+ }
+
+ /**
+ * Converts an image to another MIME type.
+ *
+ * Use {@link ezcImageMethodcallHandler::allowsOutput()} to determine,
+ * if the output MIME type is supported by this handler!
+ *
+ * @see ezcImageMethodcallHandler::load()
+ * @see ezcImageMethodcallHandler::save()
+ *
+ * @param string $image Image reference to convert.
+ * @param string $mime MIME type to convert to.
+ * @return void
+ *
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the given MIME type is not supported by the filter.
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ */
+ public function convert( $image, $mime )
+ {
+ $oldMime = $this->getReferenceData( $image, 'mime' );
+ if ( !$this->allowsOutput( $mime ) )
+ {
+ throw new ezcImageMimeTypeUnsupportedException( $mime, 'output' );
+ }
+ $this->setReferenceData( $image, $mime, 'mime' );
+ }
+
+ /**
+ * Receive the resource of the active image reference.
+ * This method is utilized by the ezcImageFilters* class to receive the
+ * currently active resource for manipulations.
+ *
+ * @return resource The currently active resource.
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If no valid resource for the active reference could be found.
+ */
+ protected function getActiveResource()
+ {
+ $ref = $this->getActiveReference();
+ if ( ( $resource = $this->getReferenceData( $ref, 'resource' ) ) === false )
+ {
+ throw new ezcImageInvalidReferenceException( "No resource found for the active reference '{$ref}'." );
+ }
+ return $resource;
+ }
+
+ /**
+ * Replace the resource of an image reference with a new one.
+ * After filtering the current image resource might have to be replaced
+ * with a new version. This can be done using this method.
+ *
+ * @param resource(GD) $resource
+ * @return void
+ */
+ protected function setActiveResource( $resource )
+ {
+ $this->setReferenceData(
+ $this->getActiveReference(),
+ $resource,
+ 'resource'
+ );
+ }
+
+ /**
+ * Returns the currently active reference.
+ * Returns the reference which is currently marked as active. This happens
+ * either by loading a new file or by using the setActiveReference()
+ * method.
+ *
+ * @see ezcImageMethodcallHandler::setActiveReference()
+ * @see ezcImageMethodcallHandler::load()
+ * @see ezcImageMethodcallHandler::$references
+ *
+ * @throws ezcImageInvalidReferenceException
+ * No loaded file could be found or an error destroyed a loaded reference.
+ *
+ * @return string The active reference.
+ */
+ protected function getActiveReference()
+ {
+ if ( !isset( $this->activeReference ) )
+ {
+ throw new ezcImageInvalidReferenceException( 'No reference is defined as active. Either no file is loeaded, yet or an internal error destroyed the reference.' );
+ }
+ return $this->activeReference;
+ }
+
+ /**
+ * Mark the submitted image reference as active.
+ * The image reference submitted is marked as active. All following
+ * filter operations are performed on this reference.
+ *
+ * @param string $image The image reference.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If the given reference is invalid.
+ */
+ protected function setActiveReference( $image )
+ {
+ if ( !isset( $this->references[$image] ) )
+ {
+ throw new ezcImageInvalidReferenceException( 'Could not mark invalid reference as active.' );
+ }
+ $this->activeReference = $image;
+ }
+
+ /**
+ * Returns data about a reference.
+ * This gives you access to the data stored about a loaded image. You can
+ * either retrieve a certain detail (defined in the references array), with
+ * specifying it through the second parameter (the method then simply
+ * returns that detail) or retrieve all available details with leaving that
+ * parameter out.
+ *
+ * By default the following details are available:
+ *
+ * 'file' => The file name of the image loaded.
+ * 'mime' => The mime type of the image loaded.
+ * 'resource' => A resource referencing it.
+ *
+ *
+ * Of what type the resource is, may differ from handler to handler (e.g. a
+ * GD resource for the GD handler or a file path for the ImageMagick handler).
+ * You can simply store your own details be setting them and retreive them
+ * through this method.
+ *
+ * @param string $reference Reference string assigned.
+ * @param mixed $detail To receive a single detail, set to detail name.
+ * @return array Array of details if you specify $detail, else depending on
+ * the detail. If detail is not available, returns false.
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If the given reference is invalid.
+ *
+ * @see ezcImageMethodcallHandler::setReferenceData()
+ */
+ protected function getReferenceData( $reference, $detail = null )
+ {
+ if ( !isset( $this->references[$reference] ) )
+ {
+ throw new ezcImageInvalidReferenceException( "Inavlid image reference given: '{$reference}'." );
+ }
+ if ( isset( $detail ) )
+ {
+ return isset( $this->references[$reference][$detail] ) ? $this->references[$reference][$detail] : false;
+ }
+ return $this->references[$reference];
+ }
+
+ /**
+ * Set data for an image reference.
+ * This method allows you to set all data that can be retrieved through
+ * ezcImageMethodcallHandler::getReferenceData(). You can either set a single detail
+ * by providing the optional $detail parameter or submit an array containing
+ * all details at once as the value to set all details.
+ *
+ * @param string $reference Reference string of the image data.
+ * @param mixed $value The value to set.
+ * @param string $detail The name of the detail to set.
+ * @return void
+ *
+ * @throws ezcImageInvalidReferenceException
+ * If the given reference is invalid.
+ * @throws ezcBaseValueException
+ * If the given detail is invalid.
+ */
+ protected function setReferenceData( $reference, $value, $detail = null )
+ {
+ if ( !isset( $this->references[$reference] ) )
+ {
+ throw new ezcImage( "Invalid image reference given: '{$reference}'." );
+ }
+ if ( isset( $detail ) )
+ {
+ $this->references[$reference][$detail] = $value;
+ }
+ else
+ {
+ if ( !is_array( $value ) )
+ {
+ throw new ezcBaseValueException( 'value', $value, 'array' );
+ }
+ if ( !isset( $value['file'] ) )
+ {
+ throw new ezcBaseValueException( 'file', null, 'string' );
+ }
+ if ( !isset( $value['mime'] ) )
+ {
+ throw new ezcBaseValueException( 'mime', null, 'string' );
+ }
+ if ( !isset( $value['resource'] ) )
+ {
+ throw new ezcBaseValueException( 'resource', null, 'string' );
+ }
+ $this->references[$reference] = $value;
+ }
+ }
+
+ /**
+ * Create a reference entry for this file.
+ * Performs common operations on a specific file, like checking if the file
+ * exists, if it is loadable, if it's already loaded. Beside of that, it
+ * creates the reference internally, so you don't need to handle this
+ * stuff manually with the internal data structure of
+ * ezcImageMethodcallHandler::$references. It also cares for determining the MIME-
+ * type of the image and sets the newly created reference to be active.
+ *
+ * @param string $file The file to load.
+ * @param string $mime The MIME type of the file.
+ * @return string reference The reference string for this file.
+ *
+ * @throws ezcBaseFileNotFoundException
+ * If the desired file does not exist.
+ * @throws ezcBaseFilePermissionException
+ * If the desired file is not readable.
+ * @throws ezcBaseValueException
+ * If the given detail is invalid.
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the desired file has a not recognized type.
+ */
+ protected function loadCommon( $file, $mime = null )
+ {
+ if ( !is_file( $file ) )
+ {
+ throw new ezcBaseFileNotFoundException( $file );
+ }
+ if ( !is_readable( $file ) )
+ {
+ throw new ezcBaseFilePermissionException( $file, ezcBaseFileException::READ );
+ }
+
+ $file = realpath( $file );
+ $ref = md5( $file );
+
+ if ( !isset( $mime ) )
+ {
+ $mime = '';
+ try
+ {
+ $analyzer = new ezcImageAnalyzer( $file );
+ $mime = $analyzer->mime;
+ }
+ catch ( ezcImageAnalyzerException $e )
+ {
+ throw new ezcImageMimeTypeUnsupportedException( 'unknown/unknown', 'input' );
+ }
+ }
+
+ $this->references[$ref] = array();
+ $this->setReferenceData(
+ $ref,
+ array(
+ 'file' => $file,
+ 'mime' => $mime,
+ 'resource' => false,
+ )
+ );
+ $this->setActiveReference( $ref );
+
+ return $ref;
+ }
+
+ /**
+ * Performs common operations before saving a file.
+ * This method should/can be used while implementing the save method of an
+ * ezcImageMethodcallHandler. It performs several tasks, like setting the new file name,
+ * if it has been submitted, and the new MIME type. Beside that, it checks
+ * if one can write to the new file and if the handler is able to process
+ * the new MIME type.
+ *
+ * @param string $reference The image reference.
+ * @param string $newFile The new filename.
+ * @param string $mime The new MIME type.
+ * @return void
+ *
+ * @throws ezcBaseFilePermissionException
+ * If the desired file is not writeable.
+ * @throws ezcImageMimeTypeUnsupportedException
+ * If the desired MIME type is not recognized.
+ */
+ protected function saveCommon( $reference, $newFile = null, $mime = null )
+ {
+ if ( isset( $newFile ) )
+ {
+ $this->setReferenceData( $reference, $newFile, 'file' );
+ }
+ $file = $this->getReferenceData( $reference, 'file' );
+ if ( file_exists( $file ) && !is_writeable( $file ) )
+ {
+ throw new ezcBaseFilePermissionException( $file, ezcBaseFileException::WRITE );
+ }
+
+ if ( isset( $mime ) )
+ {
+ $this->setReferenceData( $reference, $mime, 'mime' );
+ }
+ $mime = $this->getReferenceData( $reference, 'mime' );
+ if ( $this->allowsOutput( $mime ) === false )
+ {
+ throw new ezcImageMimeTypeUnsupportedException( $mime, 'output' );
+ }
+ }
+
+ /**
+ * Unsets the reference data for the given reference.
+ * This method _must_ be called in the implementation of the close() method
+ * in every ezcImageMethodcallHandler to finally remove the reference.
+ *
+ * @param string $reference The reference to free.
+ * @return void
+ */
+ protected function closeCommon( $reference )
+ {
+ $data = $this->getReferenceData( $reference );
+ unset( $this->references[$reference] );
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageConversion/src/interfaces/thumbnail.php b/include/ezcomponents/ImageConversion/src/interfaces/thumbnail.php
new file mode 100644
index 000000000..94faa7360
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/interfaces/thumbnail.php
@@ -0,0 +1,70 @@
+
+ * array(
+ * 0 => array( 255, 0, 0 ) for example would be pure red.
+ *
+ * @package ImageConversion
+ * @version 1.3.5
+ */
+class ezcImageSaveOptions extends ezcBaseOptions
+{
+ /**
+ * Properties.
+ *
+ * @var array(string=>mixed)
+ */
+ protected $properties = array(
+ "compression" => null,
+ "quality" => null,
+ "transparencyReplacementColor" => null,
+ );
+
+ /**
+ * Property set access.
+ *
+ * @param string $propertyName
+ * @param string $propertyValue
+ * @ignore
+ * @return void
+ */
+ public function __set( $propertyName, $propertyValue )
+ {
+ switch ( $propertyName )
+ {
+ case "compression":
+ if ( ( !is_int( $propertyValue ) || $propertyValue < 0 || $propertyValue > 9 ) && $propertyValue !== null )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, "int > 0 and < 10" );
+ }
+ break;
+ case "quality":
+ if ( ( !is_int( $propertyValue ) || $propertyValue < 0 || $propertyValue > 100 ) && $propertyValue !== null )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, "int > 0 and <= 100" );
+ }
+ break;
+ case "transparencyReplacementColor":
+ if ( ( !is_array( $propertyValue ) || count( $propertyValue ) < 3 || !isset( $propertyValue[0] ) || !isset( $propertyValue[1] ) || !isset( $propertyValue[2] ) ) && $propertyValue !== null )
+ {
+ throw new ezcBaseValueException( $propertyName, $propertyValue, "array(int)" );
+ }
+ break;
+ default:
+ throw new ezcBasePropertyNotFoundException( $propertyName );
+ }
+ $this->properties[$propertyName] = $propertyValue;
+ }
+}
+
+?>
diff --git a/include/ezcomponents/ImageConversion/src/structs/converter_settings.php b/include/ezcomponents/ImageConversion/src/structs/converter_settings.php
new file mode 100644
index 000000000..d411cf907
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/structs/converter_settings.php
@@ -0,0 +1,71 @@
+
+ * array(
+ * 'image/gif' => 'image/png', // Note: lower case!
+ * 'image/bmp' => 'image/jpeg',
+ * )
+ *
+ *
+ * @var array
+ */
+ public $conversions = array();
+
+ /**
+ * Create a new instance of ezcImageConverterSettings.
+ * Create a new instance of ezcImageConverterSettings to be used with
+ * {@link ezcImageConverter} objects..
+ *
+ * @see ezcImageConverterSettings::$handlers
+ * @see ezcImageConverterSettings::$conversions
+ *
+ * @param array $handlers Array of {@link ezcImageHandlerSettings handler objects}.
+ * @param array $conversions Map of standard MIME type conversions.
+ */
+ public function __construct( array $handlers = array(), array $conversions = array() )
+ {
+ $this->handlers = $handlers;
+ $this->conversions = $conversions;
+ }
+}
+?>
diff --git a/include/ezcomponents/ImageConversion/src/structs/filter.php b/include/ezcomponents/ImageConversion/src/structs/filter.php
new file mode 100644
index 000000000..4f594624b
--- /dev/null
+++ b/include/ezcomponents/ImageConversion/src/structs/filter.php
@@ -0,0 +1,81 @@
+
+ * q c #FF000F",
+",q c #FF0110",
+"'q c #FF0111",
+")q c #FF0213",
+"!q c #FE0314",
+"~q c #FE0315",
+"{q c #FE0418",
+"]q c #FD071D",
+"^q c #FE081D",
+"/q c #FE0920",
+"(q c #FD0921",
+"_q c #FD0A24",
+":q c #FD0C26",
+" , ' ) ! ~ { ] ^ / ( _ : < [ } | 1 2 3 4 5 6 7 8 9 0 a b c d e f g h i j k l ",
+" . . . . . . . . . . . . . m n @ # o % p q r - s , t u ) v ~ w x ^ y z _ A B } C D 1 E F 5 G H I J K L a M d d f N O P i k l Q R S ",
+" . . . . . . . . . . . m n @ T % U * = - V W t ' ) ) v X ] ^ / z _ Y A Z ` | .2 ..+.5 H I 8 @.0 a M #.d e $.g P i j k l Q %.&.*.=. ",
+" . . . . . . . . m -.@ $ ;.% p * r V ; >.' ' ) ,.~ w ^ ^ y z : Y B } C .1 3 ..5 G 6 I @.K '.a ).c e !.$.O P i j ~.Q {.&.].=.^./.(. _.:.<.[.}.|.1.2.3.4.5.6.7.8.9.0.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.A.B.C.D.E.F.G.H.I.J.K.L.M.N.O.P.Q.R.S.T.U.V.W.X.Y.Z.`. +.+++@+#+$+%+&+*+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+[+}+|+1+1+2+2+1+2+1+1+ ",
+" . . . . . . -.n # $ 3+p * = - V > t ' u ! ~ X w ] 4+z _ Y B 5+} D .2 E +.5 6+7 8 J 0 L ).#.e e $.g O i j k l Q %.&.=.7+8+/.(.9+0+ a+b+c+d+e+f+g+h+i+j+k+l+m+n+o+p+q+r+s+t+u+v+w+x+y+z+A+B+C+D+E+F+G+H+I+J+K+L+M+N+O+P+Q+R+S+T+U+V+W+X+Y+Z+`+ @.@+@@@#@$@%@&@*@=@-@;@>@,@'@)@!@~@{@]@^@/@(@_@:@<@[@}@|@1@2@3@4@5@6@7@8@9@0@a@b@c@d@e@f@g@2+1+2+h@1+1+1+ ",
+" . . . m -.@ $ ;.& p i@j@V ; >.k@u ! ! l@w x m@( z : Y B } ` D 1 2 F 5 G H 8 @.9 '.L M d e n@N O P i k k Q {.&.].=.7+8+o@9+p@q@r@s@ t@u@v@w@x@y@z@A@B@C@D@E@F@G@H@I@J@K@L@M@N@O@P@Q@R@S@T@U@V@W@X@Y@Z@`@ #.#+#@###$#%#*#=#-#;#>#,#'#)#!#~#{#]#^#/#(#_#:#<#[#}#|#1#2#3#4#5#6#7#8#9#0#a#++b#c#$+d#e#f#g#h#i#j#k#l#m#n#o#p#q#r#s#t#u#v#w#x#y#1+2+2+1+1+1+ ",
+" . m z#@ $ % & i@A#- ; W >.' B#! ~ { C#^ y y D#: B } C | .2 F +.5 H I @.@.0 L b ).e !.$.g O E#j F#G#{.%.*.].7+H#/.9+p@I#q@r@s@J#K# L#M#N#O#P#Q#R#S#T#U#V#W#j+X#Y#Z#7.`# $.$+$@$#$$$%$w+&$*$=$-$;$>$,$'$)$!$~${$]$^$/$($_$:$<$[$}$|$1$2$3$4$5$6$7$8$9$0$a$b$c$d$e$f$g$h$U.;@i$j$k$l$m$n$o$p$q$r$s$t$u$v$w$x$y$z$A$B$C$D$E$F$G$H$I$J$K$L$M$N$O$P$1+2+1+1+ ",
+" @ $ o U * A#r V W , t ' ) ! ~ C#Q$^ ( _ : A [ } ` D 2 3 F 5 5 H 8 @.K L a #.#.e f $.O P i k l G#{.%.S =.7+R$(.9+p@q@r@S$T$J#U$V$W$ X$Y$Z$`$ %.%+%@%#%$%%%h+&%*%E@=%-%;%>%,%'%)%!%t+~%{%]%^%/%(%_%:%<%[%}%|%1%2%3%4%5%6%7%8%9%0%a%b%c%d%e%f%g%h%i%j%k%l%m%n%o%p%q%r%s%t%u%2#3#v%w%x%y%z%A%{@B%C%D%E%F%G%H%I%J%K%L%M%N%O%P%Q%R%S%T%U%V%W%X%Y%Z%`% &1+1+2+ ",
+" 3+& * q - s W >.' u ! v { C#^ y z _ Y B 5+` .&1 2 3 4 5 6 7 8 9 +&L a c d e f g h P j l l R %.*.*.=.H#/.9+9+p@@&r@T$T$K#U$W$#&$&%& &&*&=&-&;&>&,&_.'&)&!&~&{&]&^&/&F@5.(&I@_&:&<&a.[&}&d.Q@|&1&2&3&4&5&6&7&n.o.8&9&0&a&b&c&d&e&f&y.z.g&h&i&j&E.F.k&l&m&n&o&p&M.q&r&P.s&R.S.t&u&v&W.w&x&y&`. +.+++z+s$A&B&C&D&-+E&F&G&H&)+!+~+I&J&K&L&M&_+N&<+O&P&|+1+ ",
+" i@= V W , ' u B#v { w ] ^ y _ : A 5+} .&D 1 2 F +.G H I J 0 0 a ).d e $.$.O P j k k Q %.S S =.8+/.(.9+p@q@r@r@S$J#V$Q&$&R&%&S&T& U&V&W&X&Y&Z&`& *.*+*@*e+#*$*%*&***=*l+-*;*>*,*q+'*)*!*~*v+{*]*^*/*(*B+_*:*<*[*}*H+|*1*2*3*M+4*5*6*7*R+8*T+9*0*W+X+a*b*c* @d*+@b$e*f*%@f$g*h*-@;@i*j*'@k*!@l*o$]@^@m*n*o*p*q*r*x$|@s*2@3@t*u*6@7@8@v*w*a@b@x*y*e@z*A* ",
+" ; W t ' ) ! ~ { x ^ / ( : B*B 5+` | 1 2 F 4 5 6+I 8 @.0 L M c d !.$.g O E#j k ~.{.%.S ].7+8+R$(.9+I#@&r@S$T$U$V$Q&$&R&%&C*D*E*F* G*H*I*J*K*L*M*t@N*O*P*Q*R*S*T*U*V*W*X*Y*Z*`*I@J@ =.=+=@=O@P@Q@R@/%#=U@$=W@%=&=Z@*===-=;=>=,='=)=*#!=-#;#>#,#'#~={=]=^=/=(=_=:=<=q&O.[#[=|#1#}=3#4#|=1=7#y&9#2=3=4=5=6=7=8=9=0=a=K%i#b=c=d=e=n#f=g=q#r#h=t#i=v#j=k= ",
+" ' l=! ! ~ { ] ^ y z : A 5+} .& .2 3 F 5 G I m=J 9 0 a M d e !.g O n=o=j l G#%.p=].=.7+8+(.9+p@@&r@r@T$J#V$V$W$q=q=%&S&T&D*E*r=s=s= t=u=v=w=x=y=z=A=B=C=D=E=Q#F=G=T#H=V#I=j+J=5.K=7.L=9.0.M=@$N=~*O=|&g.P==$Q=R=>$S=T=U=V=~$W=X=Y=Z=`= -.-+-[$@-|$#-2$$-%-&-6$*-=-9$--a$@@;->-,-'-)-=@U.!-~-j$k${-!@l*o$p$q$]-s$t$u$v$^-/-(-_-:-<-[-D$E$F$}-H$I$|-1-L$2- ",
+" ! ~ X ] ^ 4+( _ B*< } ` .&1 3-F +.4-H I 8 @.0 L M #.5-!.!.O O E#j k l R %.].6-7+H#R$(.7-I#@&r@s@J#U$Q&W$#&q=8-C*9-D*E*r=0-s=a-b-c- d-e-f-g-h-i-j-k-Y$Z$l-N*m-+%@%n-$%g+h+o-p-q-=%G@r->%s-q+)%!%t-u-v-w-]*/%(%x-y-z-[%A-B-1%C-D-4%E-F-G-8%9%H-I-J-K-L-M-N-O-P-i%j%Q-R-m%S-@@T-<#U-V-|#W-2#X-Y-Z-`-y% ;.;0#]@C%+;(@@;#;H%$;%;&;L%*;=;O%P%Q%-;;;T%>;,;';); ",
+" C#] m@y _ : Y B } ` .1 E F 5 G 6 8 @.9 L a c 5-e !.g O P i k l R {.&.*.=.8+H#(.7-p@@&@&!;~;J#Q&W$#&q=R&%&C*T&D*E*{;s=];b-c-^;/;(; _;:;<;[;};|;1;&&2;3;4;;&5;,&6;7;<.8;}.U*9;2.0;a;b;(&I@c;9.0.+=d;e;d.e.|&S@f;3&g;5&l.m.h;i;j;q.0&k;l;m;n;e&x.o;p;q;B.r;s;t;u;k&l&m&J.o&p&M.N.r&P.v;R.S.T.w;v&x;w&x&l$`.y;.+p$z&z;$+A;B;*+=+C;i#F&D;H&E;F;~+I&J&^+G;H; ",
+" ( z : Y B 5+` .1 2 ..+.5 6+7 8 J 0 L ).#.d e f g P i j l G#{.%.*.=.7+8+o@(.p@q@@&S$S$T$K#Q&W$#&q=%&C*T&I;J;r=0-s=];K;^;^;L;M;N;O; P;Q;R;S;T;U;V;U&V&W;X;Y;Z&Z;a+`; >d+e+.>+>V#i+@>k+Y##>n+$>p+q+%>)*!*&>*>w+x+y+/*=>B+_*-><*[*;>H+|*1*2*3*>>4*5*P+Q+,>8*'>)>!>~>{>Y+Z+c* @]>+@@@#@f*e$f$t%^>U./>(>j$_>)@z%:>o$<>^@m*[>_@}>|>1>2>3>4>5>3@6>u*6@7>8>9>0> ",
+" : B 5+} .& .2 3 F 5 6+7 8 @.K 0 a #.c a>!.g g P E#j l G#{.&.*.=.8+8+(.9+p@q@r@S$T$J#K#b>W$#&R&%&9-9-E*E*c>0-d>];c-^;/;(;e>f>g>h>i> j>k>l>m>n>o>p>q>r>I*s>t>u>M*t@N*O*w@v>w>S*x>y>C@2.E@Y*z>6.I@A>K@B>!%b.}&v-Q@R@C>#=D>V@W@X@E>F>G>H>I>J>K>L>$#M>N>O>P>-#;#Q>R>S>T>U>]={#/=V>W>X>Y>q&Z>P.}#R.1#`>3# ,Z-6#7#.,9#{@a#+++,@,7=#,H%$,a=%,&,*,k#=,m#-,;,>,q# ",
+" } ` | 1 3-F 4 5 G I 8 9 +&L b c d a>f g O E#j k G#{.%.S =.=.8+R$9+7-q@@&!;T$T$K#V$W$,,R&8-S&9-I;E*{;s=s=a-b-c-^;L;(;e>O;h>i>i>',), !,~,{,],^,/,(,_,:,<,[,},|,1,2,3,C=D=4,5,F=G=6,U#V#7,j+X#5.8,9,0, $<&+$a,b,&>c,w+d,P=e,-$f,g,,$T=h,!$~$i,a&j,k,`=_$:$l,m,n,|$o,p,q,r,s,6$7$t,u,0$v,b$;-w,Q.'-S.t&x,y,W.j$z,{-A,n$B,<>C,D,s$E,&+F,G,H,I,J,K,<-L,M,E$N, ",
+" D 1 3 ..5 G 7 I @.+&L M ).d e f f O i o=k l Q {.&.S =.7+R$o@9+q@q@@&s@T$J#V$W$#&$&$&%&C*D*D*{;r=s=s=K;c-^;L;(;e>f>O;h>i>',O,),P,Q, R,S,T,U,V,W,X,Y,Z,f-`, '.'j-k-+'@'#' %$'%'@%n-$%g+h+&'*'='Y*G@-'>%s-'%;'>'!*~%{%,'^%/%''x-)'z-!'A-~'1%{'3%4%]'F-7%^'/'0%('_'c%:'<'['}'h%|'1'k%2'3'S-4'T-q%[#V-|#W-2#X-5'Z-`-6' ;7'8'9'C%+;(@0'a'b'c'%;d'e'*;f'O%g'Q% ",
+" ..4 G H I 8 @.0 L b c e e $.O O P k l l {.%.&.*.7+H#(.(.p@q@@&r@S$J#J#Q&W$#&q=h'S&C*D*E*r=0-s=];i'c-/;L;e>e>j'O;k'',l'O,),m'm'Q,n' o'p'q'r's't'u'v'w'<;[;x'|;y'z'A'=&-&B'5;v@_.'&C'D'E'B@F'2./&4.G'H'I'8. =<&J'N@}&K'L'R@g.M'3&N'5&6&O'P'Q'8&q.R'S'b&T'd&e&x.y.z.q;B.U'D.V'u;W'l&I.n&K.X'Y'Z'O.P.}#R.`'}=u&v&~- )x&l$.)+).+@)5=#+$+A;&+F,#)-+$)>+%)&)*) ",
+" G 7 m==)=)=)=)-)d e n@g O P j k ~.G#%.&.S ;)8+H#(.9+p@q@@&s@T$J#K#W$W$$&=)=)=)=)>)E*c>0-];K;b-^;/;(;e>g>O;h>l'l'O,,),)m'')))!)~){) ])^)/)()_):)<)P;[)})S;T;|)V;1)2)W;3)4)5)6)7)8)c+@*e+f+$*h+i+@>9)=)=)=)=)0)a)'*@$b)u+%$w+c)d)/*=>B+e)f)<*[*}*g)|*h)2*3*M+i)5*P+Q+R+8*j)k)V+W+l)a*Z+m)n)d*+@@@#@w,%@o)t%p)-@q)i*r)k$)@s)l*t)p$^@m*[>u)u$|>v)}@3>1@5>3@ ",
+" =)=)=)=)w) 8 9 0 =)=)=)=)x)$.O P E#y)z)z)A)B)*.*.7+8+R$(.7-C)C)D)D)E)K#Q&W$#&q=%&S&=)=)=)=)F)];a-K;c-^;G)e>e>g>h>h>',O,H)H)m'Q,n'I)!)J)K)K)L) w)w)w)w)M) N)O)P)o'Q)R)S)T)U)V)W)X)Y)Z)`) !I*s>K*u>M*.!+!@!#!$!y@S*%!&!&'*!=)=)=)=)=!-!'%;!!%N@u->!L',!'!)!!!~!{!X@&=]!^! #I>/!(!_!:!M>N>*#|!]=1!/=V>2!3!4!5!O.[#}#|#1#6!w;v%7!6#7#8!A%2=a#+++,c#7=9!9=$,0!K%e'b= ",
+" =)=)=)=)w) '.b M =)=)=)=)a!P j k l =)=)=)=)b!7+R$(.9+p@@&@&=)=)=)=)c!#&$&8-%&S&T&D*=)=)=)=)d!c-^;/;G)e>f>O;h>k'',),),,)m'')n'!)~){)K)e!f!g!h! =)=)=)=)w) i!j!k!O)o'l!m!!,n!o!p!=)=)=)=)q!<,r!s!t!z=A=3,u!v!E=5,F=G=T#H=%*=)=)=)=)w!x!y! $0.+$z!e;&>%$w+&$P==$Q=f,>$A!'$B!!$C!D!]$E!/$F!G!H!I!J!n,|$K!p,3$L!M!N!7$t,9$O!v,N.P!w,e$Q!R!h$x,S!i*T!k$U!A,V!B,p$q$D,s$t$W!X!^-/-E& ",
+" =)=)=)=)w) c d !.Y!Z!`! ~.~k Q {.%.=)=)=)=)+~(.p@q@@&s@~;T$=)=)=)=)@~%&S&9-D*E*r=c>=)=)=)=)#~G)e>f>g>O;i>',O,O,,),)m'Q,!)!)$~K)K)%~%~g!h!&~*~ =)=)=)=)w) =~-~;~>~O),~'~R,)~!~~~=)=)=)=)Y)f-g-{~]~j-^~Y$/~`$(~m-_~:~<~[~%%}~|~1~2~3~G@;*>%p+a)'*!%4~u-v-{*5~6~7~8~)'9~0~a~|%b~2%D-c~d~F-e~f~9%g~h~J-c%d%M-i~g%j~k~l~m~n~m%S-4!o~<#[#V-|#u%p~q~>@j*6#6' ;~@8'a#C%+;c#F%G%r~c'%; ",
+" =)=)=)=)w) !.g O n=i j l R {.S ].=.=)=)=)=)C)@&r@s@s~J#V$Q&=)=)=)=)t~T&E*r=r=s=];b-=)=)=)=)u~g>g>h>i>',),),,)m'Q,!)I)~){)v~e!f!g!h!&~&~w~x~y~ =)=)=)=)w) z~A~B~C~D~E~,~o'F~q'r'=)=)=)=)G~H~[;G*|;I~z'2;J~K~L~M~,&_.'&C'[.N~B@1.2./&F@5.H@9,-!:&B>a.N@e;P@e.|&S@M'i.N'O~P~7&Q~R~S~T~U~k;l;c&n;e&x.V~z.A.W~i&D.V'X~k&l&Y~n&o&Z~Y'N.`~P. {Q!S.h$u&.{+{@{#{Z..)2=${++%{#+&{%+&+*+ ",
+" =)=)=)=)w) h n=o==)=)=)=)*{=.7+=)=)=)=)=)=)=)=)=)=)=)W$=)=)=)=)=)=)=)=)=)=)=)K;c-^;=)=)=)=)={k'l'O,),,)P,-{;{>{,{'{){!{~{{{g!]{^{/{w~x~y~({_{ =)=)=)=)=)=)=)=)=)=)=) :{<{[{}{|{1{2{3{4{ 5{6{7{8{9{0{a{b{c{d{e{O)f{=)=)=)=)=)=)=)=)=)=)=)g{U&V&W;h{4)i{Z; *j{c+d+=)=)=)=)k{p-9)Y#8,=)=)=)=)l{m{n{o{p{q{r{s{t{(*u{v{w{x{y{z{A{B{J+K+3*M+4*C{D{E{F{G{H{H{I{J{K{L{M{m)N{O{+@@@#@P{Q{R{S{T{U{V{i*=)=)=)=)W{o$]@^@m*[>u)X{<{[{}{|{1{2{3{4{ ",
+" =)=)=)=)w) k l R =)=)=)=)+~8+(.=)=)=)=)=)=)=)=)=)=)=)8-=)=)=)=)=)=)=)=)=)=)=)/;(;e>=)=)=)=)Y{O,P,m'm'Z{`{ ]=)=)=)=)=)=)=).]+]*~w~y~({_{@]@]#] =)=)=)=)=)=)=)=)=)=)=) $]%]&]=)=)=)=)=)=)=)*]=] -];]=)=)=)=)=)=)=)=)=)>],]']=)=)=)=)=)=)=)=)=)=)=))]!]~]I*{]t>u>M*]]^]@!/]=)=)=)=)(]_]*'0;Y*=)=)=)=):]<]=)=)=)=)=)[]}]|]1]=)=)=)=)=)2]3]I>4]@#5]6]7]=)=)=)=)=)=)=)=)=)8]9]0]/=(=_=3!a]b]=)=)=)=)=)=)c]=)=)=)=)d]7'0#a#4=e]f]g]=)=)=)=)=)=)=)*]=] ",
+" =)=)=)=)w) {.%.S =)=)=)=)h]I#0+=)=)=)=)=)=)=)=)=)=)=)S&=)=)=)=)=)=)=)=)=)=)=)f>g>O;=)=)=)=)i]m'Q,')n'j]=)=)=)=)=)=)=)=)=)=)k]l]({@]#]#]m]n]o] =)=)=)=)=)=)=)=)=)=)=) p]=)=)=)=)=)=)=)=)=)=)q]r] s]=)=)=)=)=)=)=)=)=)=)t]C~u]=)=)=)=)=)=)=)=)=)=)=)H~v]w]v=r!x]y]1,L#z]A]B]=)=)=)=)C]D]F'E]/&=)=)=)=)F]=)=)=)=)=)=)=)G]H]=)=)=)=)=)=)=)I]J]K]L]X=E!M]=)=)=)=)=)=)=)=)=)=)N]O]N!7$P]Q]R]=)=)=)=)=)=)=)S]=)=)=)=)T]{-U]V!o$V]=)=)=)=)=)=)=)=)=)=)q]r] ",
+" =)=)=)=)w) ].=.8+=)=)=)=)C)!;S$W]X]=)=)=)=)Y]Z]`] ^.^F*+^+^=)=)=)=)@^#^$^%^&^h>k'',=)=)=)=)*^n'~)$~=^=)=)=)=)-^;^>^,^'^=)=)=))^!^~^~^n]o]{^]^ ^^^^=)=)=)=)/^^^^^^^^^ (^=)=)=)=)_^:^<^[^}^=)=)=)|^ 1^=)=)=)2^3^4^5^6^7^8^t]9^-~0^a^=)=)=)=)b^c^d^e^f^g^d-h^f-i^ 'i-j^X$+'k^6)=)=)=)=)l^m^%%h+_]=)=)=)=)=)n^o^p^q^=)=)=)=)=)r^s^t^u^=)=)=)=)v^1%w^x^y^z^A^B^C^D^E^F^G^H^=)=)=)I^j~|'1'J^=)=)=)=)K^L^M^N^S]=)=)=)=)O^`-y% ;P^=)=)=)=)_^:^<^[^}^=)=)=)|^ ",
+" =)=)=)=)w) /.(.(.=)=)=)=)Q^J#K#V$W$=)=)=)=)t~T&E*r=0-s=];i'=)=)=)=)u~O;h>h>l'O,),,)=)=)=)=)R^{)K)e!S^=)=)=)T^*~w~x~x~U^=)=)=)=)V^n]o]{^W^X^Y^ =)=)=)=)w) Z^=)=)=)`^ /=)=)=)=)./ +/=)=)=)@/#/$/%/&/*/=/-/;/>/9^i!=)=)=)=),/q''/s't')/v':;!/[;};|;y'z'~/{/4;=)=)=)=)]/C'D'}.|.=)=)=)=)^///I@_&(/=)=)=)=)p{_/f.'!:/=)=)=)=)
S/=)=)=)T/U/+@b$V/W/=)=)=)=)X/q)i*j$Y/=)=)=)=)=)=)=)=)=)=)=)=)=)e/ ",
+" =)=)=)=)w) @&!;T$=)=)=)=)c!8-R&C*9-=)=)=)=)d!a-c-^;/;(;e>e>=)=)=)=)Y{),H)Z/Q,')!)~)=)=)=)=)`/g!h!^{ (=)=)=)=)=)=)=)=)=)=)=)=)=).(+(+(@(c/#($( =)=)=)=)w) %(=)=)=)=)=)=)=)=)=)=)=)=)=)2^ &(&]=)=)=)=)=)=)=)*(=(-(;(>(o/;/=)=)=)=),(,~o''()(!(j>~(V)W){(](p>^(r>/(((=)=)=)=)_(:(w@x@#%=)=)=)=)<(E@Y*[(}(=)=)=)=)|(1(O@v-Q@=)=)=)=)2({!3(4(5(6(=)=)=)=)=)=)=)=)=)=)=)=)>#}!'#8]=)=)=)7(^#_=:=Y'8(=)=)=)=)9(}=0(Y-a(=)=)=)=)=)=)=)=)=)=)=)=)=)2^ ",
+" =)=)=)=)w) T$J#K#=)=)=)=)b(S&T&E*r==)=)=)=)c(^;G)(;e>j'O;i>=)=)=)=)i]Q,')n'!)~)J)v~=)=)=)=)d(*~*~w~ (=)=)=)=)=)=)=)=)=)=)=)=)=)e(f(c/$(g(h(i( =)=)=)=)w) %(=)=)=)=)=)=)=)=)=)=)=)=)=)w) j(k(+/=)=)=)=)=)=)=)l(m(l/n(o(=)=)=)=)p(q(P)^)r(s(!,~,t(],u(w'(,v(:,v=w==)=)=)=)w(x(v!P#y(=)=)=)=)z(A(**4.5.=)=)=)=)B(M=z!C(d.=)=)=)=)D(Q=E(F(G(=)=)=)=)=)=)=)=)=)=)=)=)=)H(|$#-I(=)=)=)J(K(L(M(O!N(=)=)=)=)O(P(t&U.a(=)=)=)=)=)=)=)=)=)=)=)=)=)w) ",
+" =)=)=)=)w) Q&W$#&=)=)=)=)>)r=r=0-s==)=)=)=)Q(e>e>j'O;k'',O,=)=)=)=)*^))!){)v~e!%~R(=)=)=)=)S(y~y~T(0/=)=)=)U(V(W(X(X(Y(Z(Y(`(`( _#(g(h(._+_+_ =)=)=)=)w) d/=)=)=)@_^^^^^^^^^^^^^^^^^^#_ $_%_&_*_=_=)=)=)=)-_&/l/m/=)=)=)=);_;~,]O)>_,_'_)_T,U,!_~_{_]_h^^_/_=)=)=)=)(_Z$6) %__=)=)=)=):_&!<_*!E@=)=)=)=)0)'%[_>'}_=)=)=)=)|_(%8~)'1_=)=)=)2_3_4_5_6_7_=)=)=)=)h~,>c%8_=)=)=)9_i%j%m~0_a_=)=)=)=)b_&@t%u%c_=)=)=)@_^^^^^^^^^^^^^^^^^^#_ ",
+" =)=)=)=)w) q=R&%&=)=)=)=)d_0-];K;c-e_=)=)=)f_O;h>i>O,O,),,)'{=)=)=)g_J)v~e!e!g!h!^{=)=)=)=)h__{i_#]j_=)=)=)k_l_W^m_+(b/f(c/n_o_i(i(p_q_r_s_t_ |{=)=)=)u_ Z^=)=)=)v_$_ w_x_ y_ z_A_B_=)=)=)=)C_D_E_F_0{=)=)=)G_H_I_N)']J_K_L_q'M_s'N_O_P_Q_R_[;=)=)=)=)S_T_U_B'5;=)=)=)=)V_~&B@]&A(=)=)=)=)W_A>9.<&a.=)=)=)=)X_S@Y_3&Z_=)=)=)`_ :j;q..:+:=)=)=)=)x.@:#:$:=)=)=)%:&:1/H.I.*:=)=)=)=)=:-:}#;:>:=)=)=)v_$_ w_x_ ",
+" =)=)=)=)/^^^^^^^^^^^^^^^,:T&I;=)=)=)=)d!b-c-':G)):=)=)=)!:~:{:]:^:Z/Q,')/:=)=)=)(:_:::<:[:&~*~}:=)=)=)=)|:#]~^n]1:=)=)=)=)2:3:4:5:6:7:8:9:=)p_0:s_s_t_a:b: c:=)=)=)d:e:^^^^f: g:=)=)=)=)}^h:i:j:g:k:l:m:=) &]2{n:[^r]o:p:q:=)=)=)=)r:s:D_t:u:=)=)=)v:w:x:y:s:>~])^)z:A:B:C:D:P;w/R;S;=)=)=)=)E:W;3)Y&5)=)=)=)=)F:e+#*$*V#=)=)=)=)G:x!`#,%q+=)=)=)=)H:{*x+y+I:=)=)=)J:K:L:M:N:=)=)=)=)=)i)O:P:Q:=)=)=)=)R:S:T:U:V:=)=)=)=)W:X:w,e$Y:=)=)=)=)}^h:i:j:g:k:l:m:=) ",
+" =)=)=)=)=)=)=)=)=)=)=)=)Z:F*0-=)=)=)=)c(/;(;e>f>`:=)=)=)=)=)=)=) O;O;h>e<'{=)=)=)=)=)=) <{)J)e!f<'{=)=)=)=)=)=)g<({i_i_=)=)=)=)h<]^W^X^Y^i{*d,*$H4g}h}I4f)i}J4K4m153h) ",
+" c/#(g(i(p_q_+_+_C[s_a:b:b:m<=