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: + * + * + * In this case you would need to create the following files in + * "./repos/autoloads". Please note that the part before _autoload.php in + * the filename is the first part of the classname, not considering + * the all lower-case letter prefix. + * + * "my_autoload.php": + * + * '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 @@ + + Image map example + + + Mozilla + Mozilla + Explorer + Explorer + Opera + Opera + Safari + Safari + Konqueror + Konqueror + Mozilla + Mozilla + Explorer + Explorer + Opera + Opera + Safari + Safari + Konqueror + Konqueror + + + + 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 @@ + +Access statisticsMozillaExplorerOperaSafariKonquerorMozilla: 19113 (58.6%)Explorer: 10917 (33.5%)Opera: 1464(4.5%)Safari: 652(2.0%)Konqueror: 474(1.5%) 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 need a browser capeable of SVG to display this image. + + +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 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + ezcGraph + ezcGraph + + + 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' ); + +?> + + Image map example + + + + + + 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( \'&#x%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 associated with $char. + * + * @param string $fontPath + * @param string $char + * @return float + */ + protected function getGlyph( $fontPath, $char ) + { + // Check if glyphwidth has already been calculated. + if ( isset( $this->glyphCache[$fontPath][$char] ) ) + { + return $this->glyphCache[$fontPath][$char]; + } + + $matches = $this->fonts[$fontPath]->xpath( + $query = "glyph[@unicode=" . self::xpathEscape( $char ) . "]" + ); + + if ( count( $matches ) === 0 ) + { + // Just ignore missing glyphs. The client will still render them + // using a default font. We try to estimate some width by using a + // common other character. + return $this->glyphCache[$fontPath][$char] = + ( $char === 'o' ? false : $this->getGlyph( $fontPath, 'o' ) ); + } + + $glyph = $matches[0]; + if ( !in_array( $glyph, $this->usedGlyphs ) ) + { + $this->usedGlyphs[$fontPath][] = $glyph; + } + + // There should only ever be one match + return $this->glyphCache[$fontPath][$char] = $glyph; + } + + /** + * Returns the amount of kerning to apply for glyphs $g1 and $g2. If no + * valid kerning pair can be found 0 is returned. + * + * @param string $fontPath + * @param SimpleXMLElement $g1 + * @param SimpleXMLElement $g2 + * @return int + */ + public function getKerning( $fontPath, SimpleXMLElement $glyph1, SimpleXMLElement $glyph2 ) + { + // Get the glyph names + $g1Name = self::xpathEscape( ( string ) $glyph1['glyph-name'] ); + $g2Name = self::xpathEscape( ( string ) $glyph2['glyph-name'] ); + + // Get the unicode character names + $g1Uni = self::xpathEscape( ( string ) $glyph1['unicode'] ); + $g2Uni = self::xpathEscape( ( string ) $glyph2['unicode'] ); + + // Search for kerning pairs + $pair = $this->fonts[$fontPath]->xpath( + "svg:hkern[( @g1=$g1Name and @g2=$g2Name ) + or + ( @u1=$g1Uni and @g2=$g2Uni )]" + ); + + // If we found anything return it + if ( count( $pair ) ) + { + if ( !in_array( $pair[0], $this->usedKerns ) ) + { + $this->usedKerns[$fontPath][] = $pair[0]; + } + + return ( int ) $pair[0]['k']; + } + else + { + return 0; + } + } + + /** + * Calculates the width of $string in the current font in Em's. + * + * @param string $fontPath + * @param string $string + * @return float + */ + public function calculateStringWidth( $fontPath, $string ) + { + // Ensure font is properly initilized + $fontPath = $this->initializeFont( $fontPath ); + + $strlen = strlen( $string ); + $prevCharInfo = null; + $length = 0; + // @TODO: Add UTF-8 support here - iterating over the bytes does not + // really help. + for ( $i = 0; $i < $strlen; ++$i ) + { + // Find the font information for the character + $charInfo = $this->getGlyph( $fontPath, $string[$i] ); + + // Handle missing glyphs + if ( $charInfo === false ) + { + $prevCharInfo = null; + $length .= .5 * $this->unitsPerEm[$fontPath]; + continue; + } + + // Add the horizontal advance for the character to the length + $length += (float) $charInfo['horiz-adv-x']; + + // If we are not the first character, look for kerning pairs + if ( $prevCharInfo !== null ) + { + // Apply kerning (if any) + $length -= $this->getKerning( $fontPath, $prevCharInfo, $charInfo ); + } + + $prevCharInfo = clone $charInfo; + } + + // Divide by _unitsPerEm to get the length in Em + return (float) $length / $this->unitsPerEm[$fontPath]; + } + + /** + * Add font definitions to SVG document + * + * Add the SVG font definition paths for all used glyphs and kernings to + * the given SVG document. + * + * @param DOMDocument $document + * @return void + */ + public function addFontToDocument( DOMDocument $document ) + { + $defs = $document->getElementsByTagName( 'defs' )->item( 0 ); + + $fontNr = 0; + foreach ( $this->fonts as $path => $definition ) + { + // Just import complete font for now. + // @TODO: Only import used characters. + $font = dom_import_simplexml( $definition ); + $font = $document->importNode( $font, true ); + $font->setAttribute( 'id', 'Font' . ++$fontNr ); + + $defs->appendChild( $font ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/driver/verbose.php b/include/ezcomponents/Graph/src/driver/verbose.php new file mode 100644 index 000000000..788a77f40 --- /dev/null +++ b/include/ezcomponents/Graph/src/driver/verbose.php @@ -0,0 +1,242 @@ +options = new ezcGraphSvgDriverOptions( $options ); + echo "\n"; + } + + /** + * Draws a single polygon + * + * @param array $points + * @param ezcGraphColor $color + * @param bool $filled + * @param float $thickness + * @return void + */ + public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. ) + { + $pointString = ''; + foreach ( $points as $point ) + { + $pointString .= sprintf( "\t( %.2f, %.2f )\n", $point->x, $point->y ); + } + + printf( "% 4d: Draw %spolygon:\n%s", + $this->call++, + ( $filled ? 'filled ' : '' ), + $pointString + ); + } + + /** + * Draws a single line + * + * @param ezcGraphCoordinate $start + * @param ezcGraphCoordinate $end + * @param ezcGraphColor $color + * @param float $thickness + * @return void + */ + public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. ) + { + printf( "% 4d: Draw line from ( %.2f, %.2f ) to ( %.2f, %.2f ) with thickness %d.\n", + $this->call++, + $start->x, + $start->y, + $end->x, + $end->y, + $thickness + ); + } + + /** + * 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 ) + { + return null; + } + + /** + * Wrties text in a box of desired size + * + * @param mixed $string + * @param ezcGraphCoordinate $position + * @param mixed $width + * @param mixed $height + * @param int $align + * @param ezcGraphRotation $rotation + * @return void + */ + public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null ) + { + printf( "% 4d: Draw text '%s' at ( %.2f, %.2f ) with dimensions ( %d, %d ) and alignement %d.\n", + $this->call++, + $string, + $position->x, + $position->y, + $width, + $height, + $align + ); + } + /** + * Draws a sector of cirlce + * + * @param ezcGraphCoordinate $center + * @param mixed $width + * @param mixed $height + * @param mixed $startAngle + * @param mixed $endAngle + * @param ezcGraphColor $color + * @param bool $filled + * @return void + */ + public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ) + { + printf( "% 4d: Draw %scicle sector at ( %.2f, %.2f ) with dimensions ( %d, %d ) from %.2f to %.2f.\n", + $this->call++, + ( $filled ? 'filled ' : '' ), + $center->x, + $center->y, + $width, + $height, + $startAngle, + $endAngle + ); + } + + /** + * 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 ) + { + printf( "% 4d: Draw circular arc at ( %.2f, %.2f ) with dimensions ( %d, %d ) and size %.2f from %.2f to %.2f.\n", + $this->call++, + $center->x, + $center->y, + $width, + $height, + $size, + $startAngle, + $endAngle + ); + } + + /** + * Draws a circle + * + * @param ezcGraphCoordinate $center + * @param mixed $width + * @param mixed $height + * @param ezcGraphColor $color + * @param bool $filled + * + * @return void + */ + public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true ) + { + printf( "% 4d: Draw %scircle at ( %.2f, %.2f ) with dimensions ( %d, %d ).\n", + $this->call++, + ( $filled ? 'filled ' : '' ), + $center->x, + $center->y, + $width, + $height + ); + } + + /** + * Draws a imagemap of desired size + * + * @param mixed $file + * @param ezcGraphCoordinate $position + * @param mixed $width + * @param mixed $height + * @return void + */ + public function drawImage( $file, ezcGraphCoordinate $position, $width, $height ) + { + printf( "% 4d: Draw image '%s' at ( %.2f, %.2f ) with dimensions ( %d, %d ).\n", + $this->call++, + $file, + $position->x, + $position->y, + $width, + $height + ); + } + + /** + * Return mime type for current image format + * + * @return string + */ + public function getMimeType() + { + return 'text/plain'; + } + + /** + * Finally save image + * + * @param mixed $file + * @return void + */ + public function render ( $file ) + { + printf( "Render image.\n" ); + } +} + +?> diff --git a/include/ezcomponents/Graph/src/element/axis.php b/include/ezcomponents/Graph/src/element/axis.php new file mode 100644 index 000000000..9d5bbbd31 --- /dev/null +++ b/include/ezcomponents/Graph/src/element/axis.php @@ -0,0 +1,438 @@ +properties['nullPosition'] = false; + $this->properties['axisSpace'] = .1; + $this->properties['majorGrid'] = false; + $this->properties['minorGrid'] = false; + $this->properties['majorStep'] = null; + $this->properties['minorStep'] = null; + $this->properties['formatString'] = '%s'; + $this->properties['label'] = false; + $this->properties['labelSize'] = 14; + $this->properties['labelMargin'] = 2; + $this->properties['minArrowHeadSize'] = 4; + $this->properties['maxArrowHeadSize'] = 8; + $this->properties['labelCallback'] = null; + $this->properties['chartPosition'] = null; + $this->properties['initialized'] = false; + + parent::__construct( $options ); + + if ( !isset( $this->axisLabelRenderer ) ) + { + $this->axisLabelRenderer = new ezcGraphAxisExactLabelRenderer(); + } + } + + /** + * Set colors and border fro this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->border = $palette->axisColor; + $this->padding = $palette->padding; + $this->margin = $palette->margin; + $this->majorGrid = $palette->majorGridColor; + $this->minorGrid = $palette->minorGridColor; + } + + /** + * __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 + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'nullPosition': + $this->properties['nullPosition'] = (float) $propertyValue; + break; + case 'axisSpace': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['axisSpace'] = (float) $propertyValue; + break; + case 'majorGrid': + $this->properties['majorGrid'] = ezcGraphColor::create( $propertyValue ); + break; + case 'minorGrid': + $this->properties['minorGrid'] = ezcGraphColor::create( $propertyValue ); + break; + case 'majorStep': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['majorStep'] = (float) $propertyValue; + break; + case 'minorStep': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['minorStep'] = (float) $propertyValue; + break; + case 'formatString': + $this->properties['formatString'] = (string) $propertyValue; + break; + case 'label': + $this->properties['label'] = (string) $propertyValue; + break; + case 'labelSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 6 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 6' ); + } + + $this->properties['labelSize'] = (int) $propertyValue; + break; + case 'labelMargin': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['labelMargin'] = (int) $propertyValue; + break; + case 'maxArrowHeadSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['maxArrowHeadSize'] = (int) $propertyValue; + break; + case 'minArrowHeadSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['minArrowHeadSize'] = (int) $propertyValue; + break; + case 'axisLabelRenderer': + if ( $propertyValue instanceof ezcGraphAxisLabelRenderer ) + { + $this->axisLabelRenderer = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphAxisLabelRenderer' ); + } + break; + case 'labelCallback': + if ( is_callable( $propertyValue ) ) + { + $this->properties['labelCallback'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'callback function' ); + } + break; + case 'chartPosition': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['chartPosition'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $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 'axisLabelRenderer': + return $this->axisLabelRenderer; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Get coordinate for a dedicated value on the chart + * + * @param float $value Value to determine position for + * @return float Position on chart + */ + abstract public function getCoordinate( $value ); + + /** + * Return count of minor steps + * + * @return integer Count of minor steps + */ + abstract public function getMinorStepCount(); + + /** + * Return count of major steps + * + * @return integer Count of major steps + */ + abstract public function getMajorStepCount(); + + /** + * Get label for a dedicated step on the axis + * + * @param integer $step Number of step + * @return string label + */ + abstract public function getLabel( $step ); + + /** + * Return array of steps on this axis + * + * @return array( ezcGraphAxisStep ) + */ + public function getSteps() + { + $majorSteps = $this->getMajorStepCount(); + $minorStepsPerMajorStepCount = ( $this->getMinorStepCount() / $majorSteps ); + + $majorStepSize = 1 / $majorSteps; + $minorStepSize = ( $minorStepsPerMajorStepCount > 0 ? $majorStepSize / $minorStepsPerMajorStepCount : 0 ); + + $steps = array(); + for ( $major = 0; $major <= $majorSteps; ++$major ) + { + $majorStep = new ezcGraphAxisStep( + $majorStepSize * $major, + $majorStepSize, + $this->getLabel( $major ), + array(), + $this->isZeroStep( $major ), + ( $major === $majorSteps ) + ); + + if ( ( $minorStepsPerMajorStepCount > 0 ) && + ( $major < $majorSteps ) ) + { + // Do not add minor steps at major steps positions + for( $minor = 1; $minor < $minorStepsPerMajorStepCount; ++$minor ) + { + $majorStep->childs[] = new ezcGraphAxisStep( + ( $majorStepSize * $major ) + ( $minorStepSize * $minor ), + $minorStepSize + ); + } + } + + $steps[] = $majorStep; + } + + 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 + */ + abstract public function isZeroStep( $step ); + + /** + * Add data for this axis + * + * @param array $values + * @return void + */ + abstract public function addData( array $values ); + + /** + * Calculate axis bounding values on base of the assigned values + * + * @abstract + * @access public + * @return void + */ + abstract public function calculateAxisBoundings(); + + /** + * Render the axis + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings for the axis + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + switch ( $this->position ) + { + case ezcGraph::TOP: + $start = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $this->axisSpace ), + 0 + ); + $end = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $this->axisSpace ), + $boundings->y1 - $boundings->y0 + ); + break; + case ezcGraph::BOTTOM: + $start = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $this->axisSpace ), + $boundings->y1 - $boundings->y0 + ); + $end = new ezcGraphCoordinate( + ( $boundings->x1 - $boundings->x0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->x1 - $boundings->x0 ) * ( 1 - 2 * $this->axisSpace ), + 0 + ); + break; + case ezcGraph::LEFT: + $start = new ezcGraphCoordinate( + 0, + ( $boundings->y1 - $boundings->y0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $this->axisSpace ) + ); + $end = new ezcGraphCoordinate( + $boundings->x1 - $boundings->x0, + ( $boundings->y1 - $boundings->y0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $this->axisSpace ) + ); + break; + case ezcGraph::RIGHT: + $start = new ezcGraphCoordinate( + $boundings->x1 - $boundings->x0, + ( $boundings->y1 - $boundings->y0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $this->axisSpace ) + ); + $end = new ezcGraphCoordinate( + 0, + ( $boundings->y1 - $boundings->y0 ) * $this->axisSpace + + $this->nullPosition * ( $boundings->y1 - $boundings->y0 ) * ( 1 - 2 * $this->axisSpace ) + ); + break; + } + + $renderer->drawAxis( + $boundings, + $start, + $end, + $this, + $this->axisLabelRenderer + ); + + return $boundings; + } +} + +?> diff --git a/include/ezcomponents/Graph/src/element/background.php b/include/ezcomponents/Graph/src/element/background.php new file mode 100644 index 000000000..92ec810de --- /dev/null +++ b/include/ezcomponents/Graph/src/element/background.php @@ -0,0 +1,202 @@ + + * $chart->background->image = 'background.png'; + * + * // Image will be repeated horizontal at the top of the background + * $chart->background->repeat = ezcGraph::HORIZONTAL; + * $chart->background->postion = ezcGraph::TOP; + * + * // Image will be placed once in the center + * $chart->background->repeat = ezcGraph::NO_REPEAT; // default; + * $chart->background->position = ezcGraph::CENTER | ezcGraph::MIDDLE; + * + * // Image will be repeated all over + * $chart->background->repeat = ezcGraph::HORIZONTAL | ezcGraph::VERTICAL; + * // The position is not relevant here. + * + * + * @property string $image + * Filename of the file to use for background + * @property int $repeat + * Defines how the background image gets repeated + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphChartElementBackground extends ezcGraphChartElement +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['image'] = false; + $this->properties['repeat'] = ezcGraph::NO_REPEAT; + + 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 'image': + // Check for existance of file + if ( !is_file( $propertyValue ) || !is_readable( $propertyValue ) ) + { + throw new ezcBaseFileNotFoundException( $propertyValue ); + } + + // Check for beeing an image file + $data = getImageSize( $propertyValue ); + if ( $data === false ) + { + throw new ezcGraphInvalidImageFileException( $propertyValue ); + } + + // SWF files are useless.. + if ( $data[2] === 4 ) + { + throw new ezcGraphInvalidImageFileException( 'We cant use SWF files like <' . $propertyValue . '>.' ); + } + + $this->properties['image'] = $propertyValue; + break; + case 'repeat': + if ( ( $propertyValue >= 0 ) && ( $propertyValue <= 3 ) ) + { + $this->properties['repeat'] = (int) $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 3' ); + } + break; + case 'position': + // Overwrite parent position setter, to be able to use + // combination of positions like + // ezcGraph::TOP | ezcGraph::CENTER + if ( is_int( $propertyValue ) ) + { + $this->properties['position'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'integer' ); + } + break; + case 'color': + // Use color as an alias to set background color for background + $this->__set( 'background', $propertyValue ); + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * __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 'color': + // Use color as an alias to set background color for background + return $this->properties['background']; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Set colors and border for this element + * + * Method is overwritten because we do not ant to apply the global padding + * and margin here. + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->border = $palette->chartBorderColor; + $this->borderWidth = $palette->chartBorderWidth; + $this->background = $palette->chartBackground; + $this->padding = 0; + $this->margin = 0; + } + + /** + * Render the background + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + $boundings = $renderer->drawBox( + $boundings, + $this->background, + $this->border, + $this->borderWidth, + $this->margin, + $this->padding + ); + + if ( $this->image === false ) + { + return $boundings; + } + + $renderer->drawBackgroundImage( + $boundings, + $this->image, + $this->position, + $this->repeat + ); + + return $boundings; + } +} + +?> diff --git a/include/ezcomponents/Graph/src/element/legend.php b/include/ezcomponents/Graph/src/element/legend.php new file mode 100644 index 000000000..75858dc2e --- /dev/null +++ b/include/ezcomponents/Graph/src/element/legend.php @@ -0,0 +1,298 @@ + (string) 'Label of data element', + * 'color' => (ezcGraphColor) $color, + * 'symbol' => (integer) ezcGraph::DIAMOND, + * ), + * ... + * ) + * + * @var array + */ + protected $labels; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['portraitSize'] = .2; + $this->properties['landscapeSize'] = .1; + $this->properties['symbolSize'] = 14; + $this->properties['padding'] = 1; + $this->properties['minimumSymbolSize'] = .05; + $this->properties['spacing'] = 2; + + 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 'padding': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['padding'] = (int) $propertyValue; + break; + case 'symbolSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['symbolSize'] = (int) $propertyValue; + break; + case 'landscapeSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 1' ); + } + + $this->properties['landscapeSize'] = (float) $propertyValue; + break; + case 'portraitSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 1' ); + } + + $this->properties['portraitSize'] = (float) $propertyValue; + break; + case 'minimumSymbolSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 1' ); + } + + $this->properties['minimumSymbolSize'] = (float) $propertyValue; + break; + case 'spacing': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['spacing'] = (int) $propertyValue; + break; + default: + parent::__set( $propertyName, $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 'labels': + return $this->labels; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Generate legend from several datasets with on entry per dataset + * + * @param ezcGraphChartDataContainer $datasets + * @return void + */ + public function generateFromDataSets( ezcGraphChartDataContainer $datasets ) + { + $this->labels = array(); + foreach ( $datasets as $dataset ) + { + $this->labels[] = array( + 'label' => $dataset->label->default, + 'url' => $dataset->url->default, + 'color' => $dataset->color->default, + 'symbol' => ( $dataset->symbol->default === null ? + ezcGraph::NO_SYMBOL : + $dataset->symbol->default ), + ); + } + } + + /** + * Generate legend from single dataset with on entry per data element + * + * @param ezcGraphDataSet $dataset + * @return void + */ + public function generateFromDataSet( ezcGraphDataSet $dataset ) + { + $this->labels = array(); + foreach ( $dataset as $label => $data ) + { + $this->labels[] = array( + 'label' => $label, + 'url' => $dataset->url[$label], + 'color' => $dataset->color[$label], + 'symbol' => ( $dataset->symbol[$label] === null ? + ezcGraph::NO_SYMBOL : + $dataset->symbol[$label] ), + ); + } + } + + /** + * Calculated boundings needed for the legend. + * + * Uses the position and the configured horizontal or vertical size of a + * legend to calculate the boundings for the legend. + * + * @param ezcGraphBoundings $boundings Avalable boundings + * @return ezcGraphBoundings Remaining boundings + */ + protected function calculateBoundings( ezcGraphBoundings $boundings ) + { + $this->properties['boundings'] = clone $boundings; + + switch ( $this->position ) + { + case ezcGraph::LEFT: + $size = ( $boundings->width ) * $this->portraitSize; + + $boundings->x0 += $size; + $this->boundings->x1 = $boundings->x0; + break; + case ezcGraph::RIGHT: + $size = ( $boundings->width ) * $this->portraitSize; + + $boundings->x1 -= $size; + $this->boundings->x0 = $boundings->x1; + break; + case ezcGraph::TOP: + $size = ( $boundings->height ) * $this->landscapeSize; + + $boundings->y0 += $size; + $this->boundings->y1 = $boundings->y0; + break; + case ezcGraph::BOTTOM: + $size = ( $boundings->height ) * $this->landscapeSize; + + $boundings->y1 -= $size; + $this->boundings->y0 = $boundings->y1; + break; + } + + return $boundings; + } + + /** + * Render a legend + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings for the axis + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + $boundings = $this->calculateBoundings( $boundings ); + + if ( $this->position === ezcGraph::LEFT || $this->position === ezcGraph::RIGHT ) + { + $type = ezcGraph::VERTICAL; + } + else + { + $type = ezcGraph::HORIZONTAL; + } + + // Render standard elements + $this->properties['boundings'] = $renderer->drawBox( + $this->properties['boundings'], + $this->properties['background'], + $this->properties['border'], + $this->properties['borderWidth'], + $this->properties['margin'], + $this->properties['padding'], + $this->properties['title'], + $this->getTitleSize( $this->properties['boundings'], $type ) + ); + + // Render legend + $renderer->drawLegend( + $this->boundings, + $this, + $type + ); + + return $boundings; + } +} + +?> diff --git a/include/ezcomponents/Graph/src/element/text.php b/include/ezcomponents/Graph/src/element/text.php new file mode 100644 index 000000000..ba8c3e6ab --- /dev/null +++ b/include/ezcomponents/Graph/src/element/text.php @@ -0,0 +1,122 @@ +properties['maxHeight'] = .1; + + 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 + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'maxHeight': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['maxHeight'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } + + /** + * Render the text + * + * @param ezcGraphRenderer $renderer Renderer + * @param ezcGraphBoundings $boundings Boundings for the axis + * @return ezcGraphBoundings Remaining boundings + */ + public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ) + { + $height = (int) min( + round( $this->properties['maxHeight'] * ( $boundings->y1 - $boundings->y0 ) ), + $this->properties['font']->maxFontSize + $this->padding * 2 + $this->margin * 2 + ); + + switch ( $this->properties['position'] ) + { + case ezcGraph::TOP: + $textBoundings = new ezcGraphBoundings( + $boundings->x0, + $boundings->y0, + $boundings->x1, + $boundings->y0 + $height + ); + $boundings->y0 += $height + $this->properties['margin']; + break; + case ezcGraph::BOTTOM: + $textBoundings = new ezcGraphBoundings( + $boundings->x0, + $boundings->y1 - $height, + $boundings->x1, + $boundings->y1 + ); + $boundings->y1 -= $height + $this->properties['margin']; + break; + } + + $textBoundings = $renderer->drawBox( + $textBoundings, + $this->properties['background'], + $this->properties['border'], + $this->properties['borderWidth'], + $this->properties['margin'], + $this->properties['padding'] + ); + + $renderer->drawText( + $textBoundings, + $this->properties['title'], + ezcGraph::CENTER | ezcGraph::MIDDLE + ); + + return $boundings; + } +} + +?> diff --git a/include/ezcomponents/Graph/src/exceptions/date_parsing.php b/include/ezcomponents/Graph/src/exceptions/date_parsing.php new file mode 100644 index 000000000..eaa7887ce --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/date_parsing.php @@ -0,0 +1,33 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/exception.php b/include/ezcomponents/Graph/src/exceptions/exception.php new file mode 100644 index 000000000..86a0deb6e --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/exception.php @@ -0,0 +1,20 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/flash_bitmap_type.php b/include/ezcomponents/Graph/src/exceptions/flash_bitmap_type.php new file mode 100644 index 000000000..aa6f27e0d --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/flash_bitmap_type.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/font_rendering.php b/include/ezcomponents/Graph/src/exceptions/font_rendering.php new file mode 100644 index 000000000..163d3d38f --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/font_rendering.php @@ -0,0 +1,40 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/font_type.php b/include/ezcomponents/Graph/src/exceptions/font_type.php new file mode 100644 index 000000000..68e9e4094 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/font_type.php @@ -0,0 +1,32 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/incompatible_driver.php b/include/ezcomponents/Graph/src/exceptions/incompatible_driver.php new file mode 100644 index 000000000..4646d226c --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/incompatible_driver.php @@ -0,0 +1,34 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_assignement.php b/include/ezcomponents/Graph/src/exceptions/invalid_assignement.php new file mode 100644 index 000000000..affa6dfa4 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_assignement.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_data.php b/include/ezcomponents/Graph/src/exceptions/invalid_data.php new file mode 100644 index 000000000..e767387c3 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_data.php @@ -0,0 +1,32 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_data_source.php b/include/ezcomponents/Graph/src/exceptions/invalid_data_source.php new file mode 100644 index 000000000..9123e5e50 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_data_source.php @@ -0,0 +1,33 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_dimensions.php b/include/ezcomponents/Graph/src/exceptions/invalid_dimensions.php new file mode 100644 index 000000000..433e968af --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_dimensions.php @@ -0,0 +1,35 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_display_type.php b/include/ezcomponents/Graph/src/exceptions/invalid_display_type.php new file mode 100644 index 000000000..02681be38 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_display_type.php @@ -0,0 +1,46 @@ + 'Pie', + ezcGraph::LINE => 'Line', + ezcGraph::BAR => 'Bar', + ); + + if ( isset( $chartTypeNames[$type] ) ) + { + $chartTypeName = $chartTypeNames[$type]; + } + else + { + $chartTypeName = 'Unknown'; + } + + parent::__construct( "Invalid data set display type '$type' ('$chartTypeName') for current chart." ); + } +} + +?> diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_id.php b/include/ezcomponents/Graph/src/exceptions/invalid_id.php new file mode 100644 index 000000000..24ebec36e --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_id.php @@ -0,0 +1,32 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_image_file.php b/include/ezcomponents/Graph/src/exceptions/invalid_image_file.php new file mode 100644 index 000000000..634588080 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_image_file.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/invalid_keys.php b/include/ezcomponents/Graph/src/exceptions/invalid_keys.php new file mode 100644 index 000000000..1d080e13f --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/invalid_keys.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/no_data.php b/include/ezcomponents/Graph/src/exceptions/no_data.php new file mode 100644 index 000000000..33f77b21c --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/no_data.php @@ -0,0 +1,30 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/no_such_data.php b/include/ezcomponents/Graph/src/exceptions/no_such_data.php new file mode 100644 index 000000000..aa1f235a6 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/no_such_data.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/no_such_dataset.php b/include/ezcomponents/Graph/src/exceptions/no_such_dataset.php new file mode 100644 index 000000000..a3cd18405 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/no_such_dataset.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/no_such_element.php b/include/ezcomponents/Graph/src/exceptions/no_such_element.php new file mode 100644 index 000000000..dfc7a5a08 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/no_such_element.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/not_rendered.php b/include/ezcomponents/Graph/src/exceptions/not_rendered.php new file mode 100644 index 000000000..d5efb2f60 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/not_rendered.php @@ -0,0 +1,32 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/out_of_boundings.php b/include/ezcomponents/Graph/src/exceptions/out_of_boundings.php new file mode 100644 index 000000000..6300a1414 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/out_of_boundings.php @@ -0,0 +1,35 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/out_of_logarithmical_boundings.php b/include/ezcomponents/Graph/src/exceptions/out_of_logarithmical_boundings.php new file mode 100644 index 000000000..da06fe759 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/out_of_logarithmical_boundings.php @@ -0,0 +1,32 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/reducement_failed.php b/include/ezcomponents/Graph/src/exceptions/reducement_failed.php new file mode 100644 index 000000000..fc9877455 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/reducement_failed.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/too_many_datasets.php b/include/ezcomponents/Graph/src/exceptions/too_many_datasets.php new file mode 100644 index 000000000..a9c026ad3 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/too_many_datasets.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/unknown_color_definition.php b/include/ezcomponents/Graph/src/exceptions/unknown_color_definition.php new file mode 100644 index 000000000..199ac2f96 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/unknown_color_definition.php @@ -0,0 +1,32 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/unregular_steps.php b/include/ezcomponents/Graph/src/exceptions/unregular_steps.php new file mode 100644 index 000000000..4c254ea3a --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/unregular_steps.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/Graph/src/exceptions/unsupported_image_type.php b/include/ezcomponents/Graph/src/exceptions/unsupported_image_type.php new file mode 100644 index 000000000..34cc75432 --- /dev/null +++ b/include/ezcomponents/Graph/src/exceptions/unsupported_image_type.php @@ -0,0 +1,61 @@ + 'GIF', + 2 => 'Jpeg', + 3 => 'PNG', + 4 => 'SWF', + 5 => 'PSD', + 6 => 'BMP', + 7 => 'TIFF (intel)', + 8 => 'TIFF (motorola)', + 9 => 'JPC', + 10 => 'JP2', + 11 => 'JPX', + 12 => 'JB2', + 13 => 'SWC', + 14 => 'IFF', + 15 => 'WBMP', + 16 => 'XBM', + + ); + + if ( isset( $typeName[$type] ) ) + { + $type = $typeName[$type]; + } + else + { + $type = 'Unknown'; + } + + parent::__construct( "Unsupported image format '{$type}'." ); + } +} + +?> diff --git a/include/ezcomponents/Graph/src/graph.php b/include/ezcomponents/Graph/src/graph.php new file mode 100644 index 000000000..6ccd0e15e --- /dev/null +++ b/include/ezcomponents/Graph/src/graph.php @@ -0,0 +1,134 @@ + diff --git a/include/ezcomponents/Graph/src/interfaces/axis_label_renderer.php b/include/ezcomponents/Graph/src/interfaces/axis_label_renderer.php new file mode 100644 index 000000000..1481946ae --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/axis_label_renderer.php @@ -0,0 +1,438 @@ +properties['majorStepCount'] = false; + $this->properties['minorStepCount'] = false; + $this->properties['majorStepSize'] = 3; + $this->properties['minorStepSize'] = 1; + $this->properties['innerStep'] = true; + $this->properties['outerStep'] = false; + $this->properties['outerGrid'] = false; + $this->properties['showLabels'] = true; + $this->properties['labelPadding'] = 2; + + 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 'driver': + if ( $propertyValue instanceof ezcGraphDriver ) + { + $this->properties['driver'] = $propertyValue; + } + else + { + throw new ezcGraphInvalidDriverException( $propertyValue ); + } + break; + case 'majorStepCount': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['majorStepCount'] = (int) $propertyValue; + break; + case 'minorStepCount': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['minorStepCount'] = (int) $propertyValue; + break; + case 'majorStepSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['majorStepSize'] = (int) $propertyValue; + break; + case 'minorStepSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['minorStepSize'] = (int) $propertyValue; + break; + case 'innerStep': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['innerStep'] = (bool) $propertyValue; + break; + case 'outerStep': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['outerStep'] = (bool) $propertyValue; + break; + case 'outerGrid': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['outerGrid'] = (bool) $propertyValue; + break; + case 'showLabels': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['showLabels'] = (bool) $propertyValue; + break; + case 'labelPadding': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['labelPadding'] = (int) $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * Checks for the cutting point of two lines. + * + * The lines are given by a start position and the direction of the line, + * both as instances of {@link ezcGraphCoordinate}. If no cutting point + * could be calculated, because the lines are parallel the function will + * return false. Otherwise the factor returned can be used to calculate the + * cutting point using the following equatation: + * point = $aStart + $factor * $aDir; + * + * We return the factor instead of the resulting point because it can be + * easily determined from the factor if the cutting point is in "behind" + * the line starting point, or if the distance to the cutting point is + * bigger then the direction vector is long ( $factor > 1 ). + * + * @param ezcGraphCoordinate $aStart + * @param ezcGraphCoordinate $aDir + * @param ezcGraphCoordinate $bStart + * @param ezcGraphCoordinate $bDir + * @return mixed + */ + public function determineLineCuttingPoint( ezcGraphCoordinate $aStart, ezcGraphCoordinate $aDir, ezcGraphCoordinate $bStart, ezcGraphCoordinate $bDir ) + { + // Check if lines are parallel + if ( ( ( abs( $aDir->x ) < .000001 ) && ( abs( $bDir->x ) < .000001 ) ) || + ( ( abs( $aDir->y ) < .000001 ) && ( abs( $bDir->y ) < .000001 ) ) || + ( ( abs( $aDir->x * $bDir->x * $aDir->y * $bDir->y ) > .000001 ) && + ( abs( ( $aDir->x / $aDir->y ) - ( $bDir->x / $bDir->y ) ) < .000001 ) + ) + ) + { + return false; + } + + // Use ? : to prevent division by zero + $denominator = + ( abs( $aDir->y ) > .000001 ? $bDir->y / $aDir->y : .0 ) - + ( abs( $aDir->x ) > .000001 ? $bDir->x / $aDir->x : .0 ); + + // Solve equatation + if ( abs( $denominator ) < .000001 ) + { + return - ( + ( abs( $aDir->y ) > .000001 ? $bStart->y / $aDir->y : .0 ) - + ( abs( $aDir->y ) > .000001 ? $aStart->y / $aDir->y : .0 ) - + ( abs( $aDir->x ) > .000001 ? $bStart->x / $aDir->x : .0 ) + + ( abs( $aDir->x ) > .000001 ? $aStart->x / $aDir->x : .0 ) + ); + } + else + { + return - ( + ( abs( $aDir->y ) > .000001 ? $bStart->y / $aDir->y : .0 ) - + ( abs( $aDir->y ) > .000001 ? $aStart->y / $aDir->y : .0 ) - + ( abs( $aDir->x ) > .000001 ? $bStart->x / $aDir->x : .0 ) + + ( abs( $aDir->x ) > .000001 ? $aStart->x / $aDir->x : .0 ) + ) / $denominator; + } + } + + /** + * Draw single step on a axis + * + * Draws a step on a axis at the current position + * + * @param ezcGraphRenderer $renderer Renderer to draw the step with + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param int $axisPosition Position of axis + * @param int $size Step size + * @param ezcGraphColor $color Color of axis + * @return void + */ + public function drawStep( ezcGraphRenderer $renderer, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, $axisPosition, $size, ezcGraphColor $color ) + { + if ( ! ( $this->innerStep || $this->outerStep ) ) + { + return false; + } + + $drawStep = false; + if ( ( ( $axisPosition === ezcGraph::CENTER ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::BOTTOM ) && $this->outerStep ) || + ( ( $axisPosition === ezcGraph::TOP ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::RIGHT ) && $this->outerStep ) || + ( ( $axisPosition === ezcGraph::LEFT ) && $this->innerStep ) ) + { + // Turn direction vector to left by 90 degrees and multiply + // with major step size + $stepStart = new ezcGraphCoordinate( + $position->x - $direction->y * $size, + $position->y + $direction->x * $size + ); + $drawStep = true; + } + else + { + $stepStart = $position; + } + + if ( ( ( $axisPosition === ezcGraph::CENTER ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::BOTTOM ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::TOP ) && $this->outerStep ) || + ( ( $axisPosition === ezcGraph::RIGHT ) && $this->innerStep ) || + ( ( $axisPosition === ezcGraph::LEFT ) && $this->outerStep ) ) + { + // Turn direction vector to right by 90 degrees and multiply + // with major step size + $stepEnd = new ezcGraphCoordinate( + $position->x + $direction->y * $size, + $position->y - $direction->x * $size + ); + $drawStep = true; + } + else + { + $stepEnd = $position; + } + + if ( $drawStep ) + { + $renderer->drawStepLine( + $stepStart, + $stepEnd, + $color + ); + } + } + + /** + * Draw grid + * + * Draws a grid line at the current position + * + * @param ezcGraphRenderer $renderer Renderer to draw the grid with + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param ezcGraphColor $color Color of axis + * @return void + */ + protected function drawGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color ) + { + // Direction of grid line is direction of axis turned right by 90 + // degrees + $gridDirection = new ezcGraphCoordinate( + $direction->y, + - $direction->x + ); + + $cuttingPoints = array(); + foreach ( array( // Bounding lines + array( + 'start' => new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + 'dir' => new ezcGraphCoordinate( 0, $boundings->y1 - $boundings->y0 ) + ), + array( + 'start' => new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + 'dir' => new ezcGraphCoordinate( $boundings->x1 - $boundings->x0, 0 ) + ), + array( + 'start' => new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + 'dir' => new ezcGraphCoordinate( 0, $boundings->y0 - $boundings->y1 ) + ), + array( + 'start' => new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + 'dir' => new ezcGraphCoordinate( $boundings->x0 - $boundings->x1, 0 ) + ), + ) as $boundingLine ) + { + // Test for cutting points with bounding lines, where cutting + // position is between 0 and 1, which means, that the line is hit + // on the bounding box rectangle. Use these points as a start and + // ending point for the grid lines. There should *always* be + // exactly two points returned. + $cuttingPosition = $this->determineLineCuttingPoint( + $boundingLine['start'], + $boundingLine['dir'], + $position, + $gridDirection + ); + + if ( $cuttingPosition === false ) + { + continue; + } + + // Round to prevent minor float incorectnesses + $cuttingPosition = abs( round( $cuttingPosition, 2 ) ); + + if ( ( $cuttingPosition >= 0 ) && + ( $cuttingPosition <= 1 ) ) + { + $cuttingPoints[] = new ezcGraphCoordinate( + $boundingLine['start']->x + $cuttingPosition * $boundingLine['dir']->x, + $boundingLine['start']->y + $cuttingPosition * $boundingLine['dir']->y + ); + } + } + + if ( count( $cuttingPoints ) < 2 ) + { + // This should not happpen + return false; + } + + // Finally draw grid line + $renderer->drawGridLine( + $cuttingPoints[0], + $cuttingPoints[1], + $color + ); + } + + /** + * Modify chart boundings + * + * Optionally modify boundings of chart data + * + * @param ezcGraphBoundings $boundings Current boundings of chart + * @param ezcGraphCoordinate $direction Direction of the current axis + * @return ezcGraphBoundings Modified boundings + */ + public function modifyChartBoundings( ezcGraphBoundings $boundings, ezcGraphCoordinate $direction ) + { + return $boundings; + } + + /** + * Modify chart data position + * + * Optionally additionally modify the coodinate of a data point + * + * @param ezcGraphCoordinate $coordinate Data point coordinate + * @return ezcGraphCoordinate Modified coordinate + */ + public function modifyChartDataPosition( ezcGraphCoordinate $coordinate ) + { + return $coordinate; + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + abstract public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis + ); +} +?> diff --git a/include/ezcomponents/Graph/src/interfaces/chart.php b/include/ezcomponents/Graph/src/interfaces/chart.php new file mode 100644 index 000000000..09c103a32 --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/chart.php @@ -0,0 +1,288 @@ +palette = new ezcGraphPaletteTango(); + $this->data = new ezcGraphChartDataContainer( $this ); + + // Add standard elements + $this->addElement( 'background', new ezcGraphChartElementBackground() ); + $this->elements['background']->position = ezcGraph::CENTER | ezcGraph::MIDDLE; + + $this->addElement( 'title', new ezcGraphChartElementText() ); + $this->elements['title']->position = ezcGraph::TOP; + $this->renderElement['title'] = false; + + $this->addElement( 'legend', new ezcGraphChartElementLegend() ); + $this->elements['legend']->position = ezcGraph::LEFT; + + // Define standard renderer and driver + $this->properties['driver'] = new ezcGraphSvgDriver(); + $this->properties['renderer'] = new ezcGraphRenderer2d(); + $this->properties['renderer']->setDriver( $this->driver ); + + // Initialize other properties + $this->properties['renderedFile'] = null; + } + + /** + * Add element to chart + * + * Add a chart element to the chart and perform the required configuration + * tasks for the chart element. + * + * @param string $name Element name + * @param ezcGraphChartElement $element Chart element + * @return void + */ + protected function addElement( $name, ezcGraphChartElement $element ) + { + $this->elements[$name] = $element; + $this->elements[$name]->font = $this->options->font; + $this->elements[$name]->setFromPalette( $this->palette ); + + // Render element by default + $this->renderElement[$name] = true; + } + + /** + * 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 'title': + $this->elements['title']->title = $propertyValue; + $this->renderElement['title'] = true; + break; + case 'legend': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'boolean' ); + } + + $this->renderElement['legend'] = (bool) $propertyValue; + break; + case 'renderer': + if ( $propertyValue instanceof ezcGraphRenderer ) + { + $this->properties['renderer'] = $propertyValue; + $this->properties['renderer']->setDriver( $this->driver ); + return $this->properties['renderer']; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphRenderer' ); + } + break; + case 'driver': + if ( $propertyValue instanceof ezcGraphDriver ) + { + $this->properties['driver'] = $propertyValue; + $this->properties['renderer']->setDriver( $this->driver ); + return $this->properties['driver']; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphDriver' ); + } + break; + case 'palette': + if ( $propertyValue instanceof ezcGraphPalette ) + { + $this->properties['palette'] = $propertyValue; + $this->setFromPalette( $this->palette ); + } + else + { + throw new ezcBaseValueException( "palette", $propertyValue, "instanceof ezcGraphPalette" ); + } + + break; + case 'renderedFile': + $this->properties['renderedFile'] = (string) $propertyValue; + break; + case 'options': + if ( $propertyValue instanceof ezcGraphChartOptions ) + { + $this->options = $propertyValue; + } + else + { + throw new ezcBaseValueException( "options", $propertyValue, "instanceof ezcGraphOptions" ); + } + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } + + /** + * Set colors and border fro this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->options->font->name = $palette->fontName; + $this->options->font->color = $palette->fontColor; + + foreach ( $this->elements as $element ) + { + $element->setFromPalette( $palette ); + } + } + + /** + * __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 ) + { + if ( array_key_exists( $propertyName, $this->properties ) ) + { + return $this->properties[$propertyName]; + } + + if ( isset( $this->elements[$propertyName] ) ) + { + return $this->elements[$propertyName]; + } + + if ( ( $propertyName === 'options' ) || + ( $propertyName === 'data' ) ) + { + return $this->$propertyName; + } + else + { + throw new ezcGraphNoSuchElementException( $propertyName ); + } + } + + /** + * Returns the default display type of the current chart type. + * + * @return int Display type + */ + abstract public function getDefaultDisplayType(); + + /** + * Return filename of rendered file, and false if no file was yet rendered. + * + * @return mixed + */ + public function getRenderedFile() + { + return ( $this->renderedFile !== null ? $this->renderedFile : false ); + } + + /** + * Renders this chart + * + * Creates basic visual chart elements from the chart to be processed by + * the renderer. + * + * @param int $width + * @param int $height + * @param string $file + * @return void + */ + abstract public function render( $width, $height, $file = null ); + + /** + * 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 + * @return void + */ + abstract public function renderToOutput( $width, $height ); +} + +?> diff --git a/include/ezcomponents/Graph/src/interfaces/dataset_property.php b/include/ezcomponents/Graph/src/interfaces/dataset_property.php new file mode 100644 index 000000000..d085ffdfe --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/dataset_property.php @@ -0,0 +1,179 @@ +dataset = $dataset; + } + + /** + * Set the default value for this property + * + * @param string $name Property name + * @param mixed $value Property value + * @return void + */ + public function __set( $name, $value ) + { + if ( $name === 'default' && + $this->checkValue( $value ) ) + { + $this->defaultValue = $value; + } + } + + /** + * Get the default value for this property + * + * @param string $name Property name + * @return mixed + */ + public function __get( $name ) + { + if ( $name === 'default' ) + { + return $this->defaultValue; + } + } + + /** + * Returns if an option exists. + * Allows isset() using ArrayAccess. + * + * @param string $key The name of the option to get. + * @return bool Wether the option exists. + */ + final public function offsetExists( $key ) + { + return isset( $this->dataset[$key] ); + } + + /** + * Returns an option value. + * Get an option value by ArrayAccess. + * + * @param string $key 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 + */ + final public function offsetGet( $key ) + { + if ( isset( $this->dataValue[$key] ) ) + { + return $this->dataValue[$key]; + } + elseif ( isset( $this->dataset[$key] ) ) + { + return $this->defaultValue; + } + else + { + throw new ezcGraphNoSuchDataException( $key ); + } + } + + /** + * Set an option. + * Sets an option using ArrayAccess. + * + * @param string $key The option to set. + * @param mixed $value The value for the option. + * @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 offsetSet( $key, $value ) + { + if ( isset( $this->dataset[$key] ) && + $this->checkValue( $value ) ) + { + $this->dataValue[$key] = $value; + } + else + { + throw new ezcGraphNoSuchDataException( $key ); + } + } + + /** + * 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. + */ + final public function offsetUnset( $key ) + { + if ( isset( $this->dataset[$key] ) ) + { + unset( $this->dataValue[$key] ); + } + else + { + throw new ezcGraphNoSuchDataException( $key ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/interfaces/driver.php b/include/ezcomponents/Graph/src/interfaces/driver.php new file mode 100644 index 000000000..dc169eda9 --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/driver.php @@ -0,0 +1,740 @@ +options = $propertyValue; + } + else + { + throw new ezcBaseValueException( "options", $propertyValue, "instanceof ezcGraphOptions" ); + } + break; + + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + 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 'options': + return $this->options; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * Reduces the size of a polygon + * + * The method takes a polygon defined by a list of points and reduces its + * size by moving all lines to the middle by the given $size value. + * + * The detection of the inner side of the polygon depends on the angle at + * each edge point. This method will always work for 3 edged polygones, + * because the smaller angle will always be on the inner side. For + * polygons with more then 3 edges this method may fail. For ezcGraph this + * is a valid simplification, because we do not have any polygones which + * have an inner angle >= 180 degrees. + * + * @param array(ezcGraphCoordinate) $points + * @param float $size + * @throws ezcGraphReducementFailedException + * @return array( ezcGraphCoordinate ) + */ + protected function reducePolygonSize( array $points, $size ) + { + $pointCount = count( $points ); + + // Build normalized vectors between polygon edge points + $vectors = array(); + $vectorLength = array(); + for ( $i = 0; $i < $pointCount; ++$i ) + { + $nextPoint = ( $i + 1 ) % $pointCount; + $vectors[$i] = ezcGraphVector::fromCoordinate( $points[$nextPoint] ) + ->sub( $points[$i] ); + + // Throw exception if polygon is too small to reduce + $vectorLength[$i] = $vectors[$i]->length(); + if ( $vectorLength[$i] < $size ) + { + throw new ezcGraphReducementFailedException(); + } + $vectors[$i]->unify(); + + // Remove point from list if it the same as the next point + if ( ( $vectors[$i]->x == $vectors[$i]->y ) && ( $vectors[$i]->x == 0 ) ) + { + $pointCount--; + if ( $i === 0 ) + { + $points = array_slice( $points, $i + 1 ); + } + else + { + $points = array_merge( + array_slice( $points, 0, $i ), + array_slice( $points, $i + 1 ) + ); + } + $i--; + } + } + + // Remove vectors and appendant point, if local angle equals zero + // dergrees. + for ( $i = 0; $i < $pointCount; ++$i ) + { + $nextPoint = ( $i + 1 ) % $pointCount; + + if ( ( abs( $vectors[$i]->x - $vectors[$nextPoint]->x ) < .0001 ) && + ( abs( $vectors[$i]->y - $vectors[$nextPoint]->y ) < .0001 ) ) + { + $pointCount--; + + $points = array_merge( + array_slice( $points, 0, $i + 1 ), + array_slice( $points, $i + 2 ) + ); + $vectors = array_merge( + array_slice( $vectors, 0, $i + 1 ), + array_slice( $vectors, $i + 2 ) + ); + $i--; + } + } + + // No reducements for lines + if ( $pointCount <= 2 ) + { + return $points; + } + + // Determine one of the angles - we need to know where the smaller + // angle is, to determine if the inner side of the polygon is on + // the left or right hand. + // + // This is a valid simplification for ezcGraph(, for now). + // + // The sign of the scalar products results indicates on which site + // the smaller angle is, when comparing the orthogonale vector of + // one of the vectors with the other. Why? .. use pen and paper .. + // + // It is sufficant to do this once before iterating over the points, + // because the inner side of the polygon is on the same side of the + // point for each point. + $last = 0; + $next = 1; + + $sign = ( + -$vectors[$last]->y * $vectors[$next]->x + + $vectors[$last]->x * $vectors[$next]->y + ) < 0 ? 1 : -1; + + // Move points to center + $newPoints = array(); + for ( $i = 0; $i < $pointCount; ++$i ) + { + $last = $i; + $next = ( $i + 1 ) % $pointCount; + + // Orthogonal vector with direction based on the side of the inner + // angle + $v = clone $vectors[$next]; + if ( $sign > 0 ) + { + $v->rotateCounterClockwise()->scalar( $size ); + } + else + { + $v->rotateClockwise()->scalar( $size ); + } + + // get last vector not pointing in reverse direction + $lastVector = clone $vectors[$last]; + $lastVector->scalar( -1 ); + + // Calculate new point: Move point to the center site of the + // polygon using the normalized orthogonal vectors next to the + // point and the size as distance to move. + // point + v + size / tan( angle / 2 ) * startVector + $newPoint = clone $vectors[$next]; + $v ->add( + $newPoint + ->scalar( + $size / + tan( + $lastVector->angle( $vectors[$next] ) / 2 + ) + ) + ); + + // A fast guess: If the movement of the point exceeds the length of + // the surrounding edge vectors the angle was to small to perform a + // valid size reducement. In this case we just reduce the length of + // the movement to the minimal length of the surrounding vectors. + // This should fit in most cases. + // + // The correct way to check would be a test, if the calculated + // point is still in the original polygon, but a test for a point + // in a polygon is too expensive. + $movement = $v->length(); + if ( ( $movement > $vectorLength[$last] ) && + ( $movement > $vectorLength[$next] ) ) + { + $v->unify()->scalar( min( $vectorLength[$last], $vectorLength[$next] ) ); + } + + $newPoints[$next] = $v->add( $points[$next] ); + } + + return $newPoints; + } + + /** + * Reduce the size of an ellipse + * + * The method returns a the edgepoints and angles for an ellipse where all + * borders are moved to the inner side of the ellipse by the give $size + * value. + * + * The method returns an + * array ( + * 'center' => (ezcGraphCoordinate) New center point, + * 'start' => (ezcGraphCoordinate) New outer start point, + * 'end' => (ezcGraphCoordinate) New outer end point, + * ) + * + * @param ezcGraphCoordinate $center + * @param float $width + * @param float $height + * @param float $startAngle + * @param float $endAngle + * @param float $size + * @throws ezcGraphReducementFailedException + * @return array + */ + protected function reduceEllipseSize( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, $size ) + { + $oldStartPoint = new ezcGraphVector( + $width * cos( deg2rad( $startAngle ) ) / 2, + $height * sin( deg2rad( $startAngle ) ) / 2 + ); + + $oldEndPoint = new ezcGraphVector( + $width * cos( deg2rad( $endAngle ) ) / 2, + $height * sin( deg2rad( $endAngle ) ) / 2 + ); + + // We always need radian values.. + $degAngle = abs( $endAngle - $startAngle ); + $startAngle = deg2rad( $startAngle ); + $endAngle = deg2rad( $endAngle ); + + // Calculate normalized vectors for the lines spanning the ellipse + $unifiedStartVector = ezcGraphVector::fromCoordinate( $oldStartPoint )->unify(); + $unifiedEndVector = ezcGraphVector::fromCoordinate( $oldEndPoint )->unify(); + $startVector = ezcGraphVector::fromCoordinate( $oldStartPoint ); + $endVector = ezcGraphVector::fromCoordinate( $oldEndPoint ); + + $oldStartPoint->add( $center ); + $oldEndPoint->add( $center ); + + // Use orthogonal vectors of normalized ellipse spanning vectors to + $v = clone $unifiedStartVector; + $v->rotateClockwise()->scalar( $size ); + + // calculate new center point + // center + v + size / tan( angle / 2 ) * startVector + $centerMovement = clone $unifiedStartVector; + $newCenter = $v->add( $centerMovement->scalar( $size / tan( ( $endAngle - $startAngle ) / 2 ) ) )->add( $center ); + + // Test if center is still inside the ellipse, otherwise the sector + // was to small to be reduced + $innerBoundingBoxSize = 0.7 * min( $width, $height ); + if ( ( $newCenter->x < ( $center->x + $innerBoundingBoxSize ) ) && + ( $newCenter->x > ( $center->x - $innerBoundingBoxSize ) ) && + ( $newCenter->y < ( $center->y + $innerBoundingBoxSize ) ) && + ( $newCenter->y > ( $center->y - $innerBoundingBoxSize ) ) ) + { + // Point is in inner bounding box -> everything is OK + } + elseif ( ( $newCenter->x < ( $center->x - $width ) ) || + ( $newCenter->x > ( $center->x + $width ) ) || + ( $newCenter->y < ( $center->y - $height ) ) || + ( $newCenter->y > ( $center->y + $height ) ) ) + { + // Quick outer boundings check + if ( $degAngle > 180 ) + { + // Use old center for very big angles + $newCenter = clone $center; + } + else + { + // Do not draw for very small angles + throw new ezcGraphReducementFailedException(); + } + } + else + { + // Perform exact check + $distance = new ezcGraphVector( + $newCenter->x - $center->x, + $newCenter->y - $center->y + ); + + // Convert elipse to circle for correct angle calculation + $direction = clone $distance; + $direction->y *= ( $width / $height ); + $angle = $direction->angle( new ezcGraphVector( 0, 1 ) ); + + $outerPoint = new ezcGraphVector( + sin( $angle ) * $width / 2, + cos( $angle ) * $height / 2 + ); + + // Point is not in ellipse any more + if ( abs( $distance->x ) > abs( $outerPoint->x ) ) + { + if ( $degAngle > 180 ) + { + // Use old center for very big angles + $newCenter = clone $center; + } + else + { + // Do not draw for very small angles + throw new ezcGraphReducementFailedException(); + } + } + } + + // Use start spanning vector and its orthogonal vector to calculate + // new start point + $newStartPoint = clone $oldStartPoint; + + // Create tangent vector from tangent angle + + // Ellipse tangent factor + $ellipseTangentFactor = sqrt( + pow( $height, 2 ) * + pow( cos( $startAngle ), 2 ) + + pow( $width, 2 ) * + pow( sin( $startAngle ), 2 ) + ); + $ellipseTangentVector = new ezcGraphVector( + $width * -sin( $startAngle ) / $ellipseTangentFactor, + $height * cos( $startAngle ) / $ellipseTangentFactor + ); + + // Reverse spanning vector + $innerVector = clone $unifiedStartVector; + $innerVector->scalar( $size )->scalar( -1 ); + + $newStartPoint->add( $innerVector)->add( $ellipseTangentVector->scalar( $size ) ); + $newStartVector = clone $startVector; + $newStartVector->add( $ellipseTangentVector ); + + // Use end spanning vector and its orthogonal vector to calculate + // new end point + $newEndPoint = clone $oldEndPoint; + + // Create tangent vector from tangent angle + + // Ellipse tangent factor + $ellipseTangentFactor = sqrt( + pow( $height, 2 ) * + pow( cos( $endAngle ), 2 ) + + pow( $width, 2 ) * + pow( sin( $endAngle ), 2 ) + ); + $ellipseTangentVector = new ezcGraphVector( + $width * -sin( $endAngle ) / $ellipseTangentFactor, + $height * cos( $endAngle ) / $ellipseTangentFactor + ); + + // Reverse spanning vector + $innerVector = clone $unifiedEndVector; + $innerVector->scalar( $size )->scalar( -1 ); + + $newEndPoint->add( $innerVector )->add( $ellipseTangentVector->scalar( $size )->scalar( -1 ) ); + $newEndVector = clone $endVector; + $newEndVector->add( $ellipseTangentVector ); + + return array( + 'center' => $newCenter, + 'start' => $newStartPoint, + 'end' => $newEndPoint, + 'startAngle' => rad2deg( $startAngle + $startVector->angle( $newStartVector ) ), + 'endAngle' => rad2deg( $endAngle - $endVector->angle( $newEndVector ) ), + ); + } + + /** + * 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 + */ + abstract public function drawPolygon( array $points, ezcGraphColor $color, $filled = true, $thickness = 1. ); + + /** + * 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 + */ + abstract public function drawLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color, $thickness = 1. ); + + /** + * 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 + */ + abstract protected function getTextBoundings( $size, ezcGraphFontOptions $font, $text ); + + /** + * Test if string fits in a box with given font size + * + * This method splits the text up into tokens and tries to wrap the text + * in an optimal way to fit in the Box defined by width and height. + * + * If the text fits into the box an array with lines is returned, which + * can be used to render the text later: + * array( + * // Lines + * array( 'word', 'word', .. ), + * ) + * Otherwise the function will return false. + * + * @param string $string Text + * @param ezcGraphCoordinate $position Topleft position of the text box + * @param float $width Width of textbox + * @param float $height Height of textbox + * @param int $size Fontsize + * @return mixed Array with lines or false on failure + */ + protected function testFitStringInTextBox( $string, ezcGraphCoordinate $position, $width, $height, $size ) + { + // Tokenize String + $tokens = preg_split( '/\s+/', $string ); + $initialHeight = $height; + + $lines = array( array() ); + $line = 0; + foreach ( $tokens as $nr => $token ) + { + // Add token to tested line + $selectedLine = $lines[$line]; + $selectedLine[] = $token; + + $boundings = $this->getTextBoundings( $size, $this->options->font, implode( ' ', $selectedLine ) ); + // Check if line is too long + if ( $boundings->width > $width ) + { + if ( count( $selectedLine ) == 1 ) + { + // Return false if one single word does not fit into one line + // Scale down font size to fit this word in one line + return $width / $boundings->width; + } + else + { + // Put word in next line instead and reduce available height by used space + $lines[++$line][] = $token; + $height -= $size * ( 1 + $this->options->lineSpacing ); + } + } + else + { + // Everything is ok - put token in this line + $lines[$line][] = $token; + } + + // Return false if text exceeds vertical limit + if ( $size > $height ) + { + return 1; + } + } + + // Check width of last line + $boundings = $this->getTextBoundings( $size, $this->options->font, implode( ' ', $lines[$line] ) ); + if ( $boundings->width > $width ) + { + return 1; + } + + // It seems to fit - return line array + return $lines; + } + + /** + * If it is allow to shortened the string, this method tries to extract as + * many chars as possible to display a decent amount of characters. + * + * If no complete token (word) does fit, the largest possible amount of + * chars from the first word are taken. If the amount of chars is bigger + * then strlen( shortenedStringPostFix ) * 2 the last chars are replace by + * the postfix. + * + * If one complete word fits the box as many words are taken as possible + * including a appended shortenedStringPostFix. + * + * @param mixed $string + * @param ezcGraphCoordinate $position + * @param mixed $width + * @param mixed $height + * @param mixed $size + * @access protected + * @return void + */ + protected function tryFitShortenedString( $string, ezcGraphCoordinate $position, $width, $height, $size ) + { + $tokens = preg_split( '/\s+/', $string ); + + // Try to fit a complete word first + $boundings = $this->getTextBoundings( + $size, + $this->options->font, + reset( $tokens ) . ( $postfix = $this->options->autoShortenStringPostFix ) + ); + + if ( $boundings->width > $width ) + { + // Not even one word fits the box + $word = reset( $tokens ); + + // Test if first character fits the box + $boundigs = $this->getTextBoundings( + $size, + $this->options->font, + $hit = $word[0] + ); + + if ( $boundigs->width > $width ) + { + // That is a really small box. + throw new ezcGraphFontRenderingException( $string, $size, $width, $height ); + } + + // Try to put more charactes in there + $postLength = strlen( $postfix ); + $wordLength = strlen( $word ); + for ( $i = 2; $i <= $wordLength; ++$i ) + { + $string = substr( $word, 0, $i ); + if ( strlen( $string ) > ( $postLength << 1 ) ) + { + $string = substr( $string, 0, -$postLength ) . $postfix; + } + + $boundigs = $this->getTextBoundings( $size, $this->options->font, $string ); + + if ( $boundigs->width < $width ) + { + $hit = $string; + } + else + { + // Use last string which fit + break; + } + } + } + else + { + // Try to use as many words as possible + $hit = reset( $tokens ); + + for ( $i = 2; $i < count( $tokens ); ++$i ) + { + $string = implode( ' ', array_slice( $tokens, 0, $i ) ) . + $postfix; + + $boundings = $this->getTextBoundings( $size, $this->options->font, $string ); + + if ( $boundings->width <= $width ) + { + $hit .= ' ' . $tokens[$i - 1]; + } + else + { + // Use last valid hit + break; + } + } + + $hit .= $postfix; + } + + return array( array( $hit ) ); + } + + /** + * 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 + */ + abstract public function drawTextBox( $string, ezcGraphCoordinate $position, $width, $height, $align, ezcGraphRotation $rotation = null ); + + /** + * 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 + */ + abstract public function drawCircleSector( ezcGraphCoordinate $center, $width, $height, $startAngle, $endAngle, ezcGraphColor $color, $filled = 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 Fill state + * @return void + */ + abstract public function drawCircularArc( ezcGraphCoordinate $center, $width, $height, $size, $startAngle, $endAngle, ezcGraphColor $color, $filled = true ); + + /** + * 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 + */ + abstract public function drawCircle( ezcGraphCoordinate $center, $width, $height, ezcGraphColor $color, $filled = true ); + + /** + * Draw an image + * + * @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 + */ + abstract public function drawImage( $file, ezcGraphCoordinate $position, $width, $height ); + + /** + * Return mime type for current image format + * + * @return string + */ + abstract public function getMimeType(); + + /** + * 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( 'php://output' ); + } + + /** + * Finally save image + * + * @param string $file Destination filename + * @return void + */ + abstract public function render( $file ); +} + +?> diff --git a/include/ezcomponents/Graph/src/interfaces/element.php b/include/ezcomponents/Graph/src/interfaces/element.php new file mode 100644 index 000000000..9d93edfd4 --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/element.php @@ -0,0 +1,280 @@ +properties['title'] = false; + $this->properties['background'] = false; + $this->properties['boundings'] = new ezcGraphBoundings(); + $this->properties['border'] = false; + $this->properties['borderWidth'] = 0; + $this->properties['padding'] = 0; + $this->properties['margin'] = 0; + $this->properties['position'] = ezcGraph::LEFT; + $this->properties['maxTitleHeight'] = 16; + $this->properties['portraitTitleSize'] = .15; + $this->properties['landscapeTitleSize'] = .2; + $this->properties['font'] = new ezcGraphFontOptions(); + $this->properties['fontCloned'] = false; + + parent::__construct( $options ); + } + + /** + * Set colors and border fro this element + * + * @param ezcGraphPalette $palette Palette + * @return void + */ + public function setFromPalette( ezcGraphPalette $palette ) + { + $this->properties['border'] = $palette->elementBorderColor; + $this->properties['borderWidth'] = $palette->elementBorderWidth; + $this->properties['background'] = $palette->elementBackground; + $this->properties['padding'] = $palette->padding; + $this->properties['margin'] = $palette->margin; + } + + /** + * __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 'title': + $this->properties['title'] = (string) $propertyValue; + break; + case 'background': + $this->properties['background'] = ezcGraphColor::create( $propertyValue ); + break; + case 'border': + $this->properties['border'] = ezcGraphColor::create( $propertyValue ); + break; + case 'padding': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['padding'] = (int) $propertyValue; + break; + case 'margin': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['margin'] = (int) $propertyValue; + break; + case 'borderWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['borderWidth'] = (int) $propertyValue; + break; + case 'font': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['font'] = $propertyValue; + } + elseif ( is_string( $propertyValue ) ) + { + if ( !$this->fontCloned ) + { + $this->properties['font'] = clone $this->font; + $this->properties['fontCloned'] = true; + } + + $this->properties['font']->path = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + case 'position': + $positions = array( + ezcGraph::TOP, + ezcGraph::BOTTOM, + ezcGraph::LEFT, + ezcGraph::RIGHT, + ); + + if ( in_array( $propertyValue, $positions, true ) ) + { + $this->properties['position'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( 'position', $propertyValue, 'integer' ); + } + break; + case 'maxTitleHeight': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties['maxTitleHeight'] = (int) $propertyValue; + break; + case 'portraitTitleSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['portraitTitleSize'] = (float) $propertyValue; + break; + case 'landscapeTitleSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['landscapeTitleSize'] = (float) $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + 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 'font': + // Clone font configuration when requested for this element + if ( !$this->fontCloned ) + { + $this->properties['font'] = clone $this->properties['font']; + $this->properties['fontCloned'] = true; + } + return $this->properties['font']; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Renders this chart element + * + * This method receives and returns a part of the canvas where it can be + * rendered on. + * + * @param ezcGraphRenderer $renderer + * @param ezcGraphBoundings $boundings + * @return ezcGraphBoundings Part of canvas, which is still free to draw on + */ + abstract public function render( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings ); + + /** + * Returns calculated boundings based on available percentual space of + * given bounding box specified in the elements options and direction of + * the box. + * + * @param ezcGraphBoundings $boundings + * @param int $direction + * @return ezcGraphBoundings + */ + protected function getTitleSize( ezcGraphBoundings $boundings, $direction = ezcGraph::HORIZONTAL ) + { + if ( $direction === ezcGraph::HORIZONTAL ) + { + return min( + $this->maxTitleHeight, + ( $boundings->y1 - $boundings->y0 ) * $this->landscapeTitleSize + ); + } + else + { + return min( + $this->maxTitleHeight, + ( $boundings->y1 - $boundings->y0 ) * $this->portraitTitleSize + ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/interfaces/odometer_renderer.php b/include/ezcomponents/Graph/src/interfaces/odometer_renderer.php new file mode 100644 index 000000000..fe6f6da56 --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/odometer_renderer.php @@ -0,0 +1,51 @@ + diff --git a/include/ezcomponents/Graph/src/interfaces/palette.php b/include/ezcomponents/Graph/src/interfaces/palette.php new file mode 100644 index 000000000..d48617c5f --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/palette.php @@ -0,0 +1,284 @@ +colorIndex = -1; + $this->symbolIndex = -1; + } + + /** + * Returns the requested property + * + * @param string $propertyName Name of property + * @return mixed + * @ignore + */ + public function __get( $propertyName ) + { + switch ( $propertyName ) + { + case 'axisColor': + case 'majorGridColor': + case 'minorGridColor': + case 'fontColor': + case 'chartBackground': + case 'chartBorderColor': + case 'elementBackground': + case 'elementBorderColor': + return ( $this->$propertyName = $this->checkColor( $this->$propertyName ) ); + + case 'dataSetColor': + $this->colorIndex = ( ( $this->colorIndex + 1 ) % count( $this->dataSetColor ) ); + return $this->checkColor( $this->dataSetColor[ $this->colorIndex ] ); + case 'dataSetSymbol': + $this->symbolIndex = ( ( $this->symbolIndex + 1 ) % count( $this->dataSetSymbol ) ); + return $this->dataSetSymbol[ $this->symbolIndex ]; + + case 'fontName': + case 'chartBorderWidth': + case 'elementBorderWidth': + case 'padding': + case 'margin': + return $this->$propertyName; + + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * __set + * + * @param mixed $propertyName Property name + * @param mixed $propertyValue Property value + * @access public + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'axisColor': + case 'majorGridColor': + case 'minorGridColor': + case 'fontColor': + case 'chartBackground': + case 'chartBorderColor': + case 'elementBackground': + case 'elementBorderColor': + $this->$propertyName = ezcGraphColor::create( $propertyValue ); + break; + + case 'dataSetColor': + if ( !is_array( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'array( ezcGraphColor )' ); + } + + $this->dataSetColor = array(); + foreach ( $propertyValue as $value ) + { + $this->dataSetColor[] = ezcGraphColor::create( $value ); + } + $this->colorIndex = -1; + break; + case 'dataSetSymbol': + if ( !is_array( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'array( (int) ezcGraph::SYMBOL_TYPE )' ); + } + + $this->dataSetSymbol = array(); + foreach ( $propertyValue as $value ) + { + $this->dataSetSymbol[] = (int) $value; + } + $this->symbolIndex = -1; + break; + + case 'fontName': + $this->$propertyName = (string) $propertyValue; + break; + + case 'chartBorderWidth': + case 'elementBorderWidth': + case 'padding': + case 'margin': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->$propertyName = (int) $propertyValue; + break; + + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/interfaces/radar_renderer.php b/include/ezcomponents/Graph/src/interfaces/radar_renderer.php new file mode 100644 index 000000000..f1f34eda2 --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/radar_renderer.php @@ -0,0 +1,54 @@ + diff --git a/include/ezcomponents/Graph/src/interfaces/renderer.php b/include/ezcomponents/Graph/src/interfaces/renderer.php new file mode 100644 index 000000000..deecc300e --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/renderer.php @@ -0,0 +1,571 @@ +driver = $driver; + } + + /** + * Adds a element reference for context + * + * @param ezcGraphContext $context Dataoint context + * @param mixed $reference Driver dependant reference + * @return void + */ + protected function addElementReference( ezcGraphContext $context, $reference ) + { + $this->elements['data'][$context->dataset][$context->datapoint][] = $reference; + } + + /** + * Return all chart element references + * + * @return array chart element references + */ + public function getElementReferences() + { + return $this->elements; + } + + /** + * __get + * + * @param string $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 'xAxisSpace': + case 'yAxisSpace': + return $this->$propertyName; + case 'elements': + return $this->elements; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + } + } + + /** + * Draw pie segment + * + * Draws a single pie segment + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of pie segment + * @param float $startAngle Start angle + * @param float $endAngle End angle + * @param mixed $label Label of pie segment + * @param bool $moveOut Move out from middle for hilighting + * @return void + */ + abstract public function drawPieSegment( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + $startAngle = .0, + $endAngle = 360., + $label = false, + $moveOut = false + ); + + /** + * Draw bar + * + * Draws a bar as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $position Position of data point + * @param float $stepSize Space which can be used for bars + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param int $symbol Symbol to draw for line + * @param float $axisPosition Position of axis for drawing filled lines + * @return void + */ + abstract public function drawBar( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $position, + $stepSize, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + $axisPosition = 0. + ); + + /** + * Draw data line + * + * Draws a line as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $start Starting point + * @param ezcGraphCoordinate $end Ending point + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param int $symbol Symbol to draw for line + * @param ezcGraphColor $symbolColor Color of the symbol, defaults to linecolor + * @param ezcGraphColor $fillColor Color to fill line with + * @param float $axisPosition Position of axis for drawing filled lines + * @param float $thickness Line thickness + * @return void + */ + abstract public function drawDataLine( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + ezcGraphColor $symbolColor = null, + ezcGraphColor $fillColor = null, + $axisPosition = 0., + $thickness = 1. + ); + + /** + * Draws a highlight textbox for a datapoint. + * + * A highlight textbox for line and bar charts means a box with the current + * value in the graph. + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphCoordinate $end Ending point + * @param float $axisPosition Position of axis for drawing filled lines + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param ezcGraphFontOptions $font Font used for highlight string + * @param string $text Acutual value + * @param int $size Size of highlight text + * @param ezcGraphColor $markLines + * @return void + */ + abstract public function drawDataHighlightText( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphCoordinate $end, + $axisPosition = 0., + $dataNumber = 1, + $dataCount = 1, + ezcGraphFontOptions $font, + $text, + $size, + ezcGraphColor $markLines = null + ); + + /** + * Draw legend + * + * Will draw a legend in the bounding box + * + * @param ezcGraphBoundings $boundings Bounding of legend + * @param ezcGraphChartElementLegend $legend Legend to draw + * @param int $type Type of legend: Protrait or landscape + * @return void + */ + abstract public function drawLegend( + ezcGraphBoundings $boundings, + ezcGraphChartElementLegend $legend, + $type = ezcGraph::VERTICAL + ); + + /** + * Draw box + * + * Box are wrapping each major chart element and draw border, background + * and title to each chart element. + * + * Optionally a padding and margin for each box can be defined. + * + * @param ezcGraphBoundings $boundings Boundings of the box + * @param ezcGraphColor $background Background color + * @param ezcGraphColor $borderColor Border color + * @param int $borderWidth Border width + * @param int $margin Margin + * @param int $padding Padding + * @param mixed $title Title of the box + * @param int $titleSize Size of title in the box + * @return ezcGraphBoundings Remaining inner boundings + */ + abstract public function drawBox( + ezcGraphBoundings $boundings, + ezcGraphColor $background = null, + ezcGraphColor $borderColor = null, + $borderWidth = 0, + $margin = 0, + $padding = 0, + $title = false, + $titleSize = 16 + ); + + /** + * Draw text + * + * Draws the provided text in the boundings + * + * @param ezcGraphBoundings $boundings Boundings of text + * @param string $text Text + * @param int $align Alignement of text + * @param ezcGraphRotation $rotation + * @return void + */ + abstract public function drawText( + ezcGraphBoundings $boundings, + $text, + $align = ezcGraph::LEFT, + ezcGraphRotation $rotation = null + ); + + /** + * Draw axis + * + * Draws an axis form the provided start point to the end point. A specific + * angle of the axis is not required. + * + * For the labeleing of the axis a sorted array with major steps and an + * array with minor steps is expected, which are build like this: + * array( + * array( + * 'position' => (float), + * 'label' => (string), + * ) + * ) + * where the label is optional. + * + * The label renderer class defines how the labels are rendered. For more + * documentation on this topic have a look at the basic label renderer + * class. + * + * Additionally it can be specified if a major and minor grid are rendered + * by defining a color for them. Teh axis label is used to add a caption + * for the axis. + * + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $start Start point of axis + * @param ezcGraphCoordinate $end Endpoint of axis + * @param ezcGraphChartElementAxis $axis Axis to render + * @param ezcGraphAxisLabelRenderer $labelClass Used label renderer + * @return void + */ + abstract public function drawAxis( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphAxisLabelRenderer $labelClass = null + ); + + /** + * Draw background image + * + * Draws a background image at the defined position. If repeat is set the + * background image will be repeated like any texture. + * + * @param ezcGraphBoundings $boundings Boundings for the background image + * @param string $file Filename of background image + * @param int $position Position of background image + * @param int $repeat Type of repetition + * @return void + */ + abstract public function drawBackgroundImage( + ezcGraphBoundings $boundings, + $file, + $position = 48, // ezcGraph::CENTER | ezcGraph::MIDDLE + $repeat = ezcGraph::NO_REPEAT + ); + + /** + * Draw Symbol + * + * Draws a single symbol defined by the symbol constants in ezcGraph. for + * NO_SYMBOL a rect will be drawn. + * + * @param ezcGraphBoundings $boundings Boundings of symbol + * @param ezcGraphColor $color Color of symbol + * @param int $symbol Type of symbol + * @return void + */ + public function drawSymbol( + ezcGraphBoundings $boundings, + ezcGraphColor $color, + $symbol = ezcGraph::NO_SYMBOL ) + { + switch ( $symbol ) + { + case ezcGraph::NO_SYMBOL: + $return = $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + new ezcGraphCoordinate( $boundings->x0, $boundings->y1 ), + ), + $color, + true + ); + + // Draw optional gleam + if ( $this->options->legendSymbolGleam !== false ) + { + $this->driver->drawPolygon( + array( + $topLeft = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x1 - ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + $bottomRight = new ezcGraphCoordinate( + $boundings->x1 - ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y1 - ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y1 - ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + ), + new ezcGraphLinearGradient( + $bottomRight, + $topLeft, + $color->darken( -$this->options->legendSymbolGleam ), + $color->darken( $this->options->legendSymbolGleam ) + ), + true + ); + } + return $return; + case ezcGraph::DIAMOND: + $return = $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ), + new ezcGraphCoordinate( + $boundings->x1, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y1 + ), + new ezcGraphCoordinate( + $boundings->x0, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + ), + $color, + true + ); + + // Draw optional gleam + if ( $this->options->legendSymbolGleam !== false ) + { + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x1 - ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y1 - ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + ), + new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * 0.353553391, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * 0.353553391 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * ( 1 - 0.353553391 ), + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * ( 1 - 0.353553391 ) + ), + $color->darken( -$this->options->legendSymbolGleam ), + $color->darken( $this->options->legendSymbolGleam ) + ), + true + ); + } + return $return; + case ezcGraph::BULLET: + $return = $this->driver->drawCircle( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + $boundings->x1 - $boundings->x0, + $boundings->y1 - $boundings->y0, + $color, + true + ); + + // Draw optional gleam + if ( $this->options->legendSymbolGleam !== false ) + { + $this->driver->drawCircle( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + ( $boundings->x1 - $boundings->x0 ) * $this->options->legendSymbolGleamSize, + ( $boundings->y1 - $boundings->y0 ) * $this->options->legendSymbolGleamSize, + new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * 0.292893219, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * 0.292893219 + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) * 0.707106781, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) * 0.707106781 + ), + $color->darken( -$this->options->legendSymbolGleam ), + $color->darken( $this->options->legendSymbolGleam ) + ), + true + ); + } + return $return; + case ezcGraph::CIRCLE: + return $this->driver->drawCircle( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ), + $boundings->x1 - $boundings->x0, + $boundings->y1 - $boundings->y0, + $color, + false + ); + } + } + + /** + * Finish rendering + * + * Method is called before the final image is renderer, so that finishing + * operations can be performed here. + * + * @return void + */ + abstract protected function finish(); + + /** + * Reset renderer properties + * + * Reset all renderer properties, which were calculated during the + * rendering process, to offer a clean environment for rerendering. + * + * @return void + */ + protected function resetRenderer() + { + $this->xAxisSpace = false; + $this->yAxisSpace = false; + + // Reset driver, maintaining its configuration + $driverClass = get_class( $this->driver ); + $driverOptions = $this->driver->options; + $this->driver = new $driverClass(); + $this->driver->options = $driverOptions; + } + + /** + * Finally renders the image + * + * @param string $file Filename of destination file + * @return void + */ + public function render( $file = null ) + { + $this->finish(); + + if ( $file === null ) + { + $this->driver->renderToOutput(); + } + else + { + $this->driver->render( $file ); + } + + $this->resetRenderer(); + } +} +?> diff --git a/include/ezcomponents/Graph/src/interfaces/stacked_bar_renderer.php b/include/ezcomponents/Graph/src/interfaces/stacked_bar_renderer.php new file mode 100644 index 000000000..949d7e909 --- /dev/null +++ b/include/ezcomponents/Graph/src/interfaces/stacked_bar_renderer.php @@ -0,0 +1,46 @@ + diff --git a/include/ezcomponents/Graph/src/math/boundings.php b/include/ezcomponents/Graph/src/math/boundings.php new file mode 100644 index 000000000..36b6c8e4d --- /dev/null +++ b/include/ezcomponents/Graph/src/math/boundings.php @@ -0,0 +1,105 @@ +x0 = $x0; + $this->y0 = $y0; + $this->x1 = $x1; + $this->y1 = $y1; + + // Switch values to ensure correct order + if ( $this->x0 > $this->x1 ) + { + $tmp = $this->x0; + $this->x0 = $this->x1; + $this->x1 = $tmp; + } + + if ( $this->y0 > $this->y1 ) + { + $tmp = $this->y0; + $this->y0 = $this->y1; + $this->y1 = $tmp; + } + } + + /** + * Getter for calculated values depending on the boundings. + * - 'width': Width of bounding recangle + * - 'height': Height of bounding recangle + * + * @param string $name Name of property to get + * @return mixed Calculated value + */ + public function __get( $name ) + { + switch ( $name ) + { + case 'width': + return $this->x1 - $this->x0; + case 'height': + return $this->y1 - $this->y0; + default: + throw new ezcBasePropertyNotFoundException( $name ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/math/matrix.php b/include/ezcomponents/Graph/src/math/matrix.php new file mode 100644 index 000000000..97c4404c9 --- /dev/null +++ b/include/ezcomponents/Graph/src/math/matrix.php @@ -0,0 +1,502 @@ +rows = max( 1, (int) $rows ); + $this->columns = max( 1, (int) $columns ); + + if ( $values !== null ) + { + $this->fromArray( $values ); + } + else + { + $this->init(); + } + } + + /** + * Create matrix from array + * + * Use an array with float values to set matrix values. + * + * @param array $values Array with values + * @return ezcGraphMatrix Modified matrix + */ + public function fromArray( array $values ) + { + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] = + ( isset( $values[$i][$j] ) + ? (float) $values[$i][$j] + : 0 ); + } + } + + return $this; + } + + /** + * Init matrix + * + * Sets matrix to identity matrix. + * + * @return ezcGraphMatrix Modified matrix + */ + public function init() + { + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] = ( $i === $j ? 1 : 0 ); + } + } + + return $this; + } + + /** + * Returns number of rows + * + * @return int Number of rows + */ + public function rows() + { + return $this->rows; + } + + /** + * Returns number of columns + * + * @return int Number of columns + */ + public function columns() + { + return $this->columns; + } + + /** + * Get a single matrix value + * + * Returns the value of the matrix at the given position + * + * @param int $i Column + * @param int $j Row + * @return float Matrix value + */ + public function get( $i, $j ) + { + if ( ( $i < 0 ) || + ( $i >= $this->rows ) || + ( $j < 0 ) || + ( $j >= $this->columns ) ) + { + throw new ezcGraphMatrixOutOfBoundingsException( $this->rows, $this->columns, $i, $j ); + } + + return ( !isset( $this->matrix[$i][$j] ) ? .0 : $this->matrix[$i][$j] ); + } + + /** + * Set a single matrix value + * + * Sets the value of the matrix at the given position. + * + * @param int $i Column + * @param int $j Row + * @param float $value Value + * @return ezcGraphMatrix Updated matrix + */ + public function set( $i, $j, $value ) + { + if ( ( $i < 0 ) || + ( $i >= $this->rows ) || + ( $j < 0 ) || + ( $j >= $this->columns ) ) + { + throw new ezcGraphMatrixOutOfBoundingsException( $this->rows, $this->columns, $i, $j ); + } + + $this->matrix[$i][$j] = $value; + + return $this; + } + + /** + * Adds one matrix to the current one + * + * Calculate the sum of two matrices and returns the resulting matrix. + * + * @param ezcGraphMatrix $matrix Matrix to sum with + * @return ezcGraphMatrix Result matrix + */ + public function add( ezcGraphMatrix $matrix ) + { + if ( ( $this->rows !== $matrix->rows() ) || + ( $this->columns !== $matrix->columns() ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->rows, $this->columns, $matrix->rows(), $matrix->columns() ); + } + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] += $matrix->get( $i, $j ); + } + } + + return $this; + } + + /** + * Subtracts matrix from current one + * + * Calculate the diffenrence of two matices and returns the result matrix. + * + * @param ezcGraphMatrix $matrix subtrahend + * @return ezcGraphMatrix Result matrix + */ + public function diff( ezcGraphMatrix $matrix ) + { + if ( ( $this->rows !== $matrix->rows() ) || + ( $this->columns !== $matrix->columns() ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->rows, $this->columns, $matrix->rows(), $matrix->columns() ); + } + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] -= $matrix->get( $i, $j ); + } + } + + return $this; + } + + /** + * Scalar multiplication + * + * Multiplies matrix with the given scalar and returns the result matrix + * + * @param float $scalar Scalar + * @return ezcGraphMatrix Result matrix + */ + public function scalar( $scalar ) + { + $scalar = (float) $scalar; + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] *= $scalar; + } + } + } + + /** + * Transpose matrix + * + * @return ezcGraphMatrix Transposed matrix + */ + public function transpose() + { + $matrix = clone $this; + + $this->rows = $matrix->columns(); + $this->columns = $matrix->rows(); + + $this->matrix = array(); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $this->matrix[$i][$j] = $matrix->get( $j, $i ); + } + } + + return $this; + } + + /** + * Multiplies two matrices + * + * Multiply current matrix with another matrix and returns the result + * matrix. + * + * @param ezcGraphMatrix $matrix Second factor + * @return ezcGraphMatrix Result matrix + */ + public function multiply( ezcGraphMatrix $matrix ) + { + $mColumns = $matrix->columns(); + if ( $this->columns !== ( $mRows = $matrix->rows() ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->columns, $this->rows, $mColumns, $mRows ); + } + + $result = new ezcGraphMatrix( $this->rows, $mColumns ); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $mColumns; ++$j ) + { + $sum = 0; + for ( $k = 0; $k < $mRows; ++$k ) { + $sum += $this->matrix[$i][$k] * $matrix->get( $k, $j ); + } + + $result->set( $i, $j, $sum ); + } + } + + return $result; + } + + /** + * Solve nonlinear equatation + * + * Tries to solve equatation given by two matrices, with assumption, that: + * A * x = B + * where $this is A, and the paramenter B. x is cosnidered as a vector + * x = ( x^n, x^(n-1), ..., x^2, x, 1 ) + * + * Will return a polynomial solution for x. + * + * See: http://en.wikipedia.org/wiki/Gauss-Newton_algorithm + * + * @param ezcGraphMatrix $matrix B + * @return ezcGraphPolygon Solution of equatation + */ + public function solveNonlinearEquatation( ezcGraphMatrix $matrix ) + { + // Build complete equatation + $equatation = new ezcGraphMatrix( $this->rows, $columns = ( $this->columns + 1 ) ); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $this->columns; ++$j ) + { + $equatation->set( $i, $j, $this->matrix[$i][$j] ); + } + $equatation->set( $i, $this->columns, $matrix->get( $i, 0 ) ); + } + + // Compute upper triangular matrix on left side of equatation + for ( $i = 0; $i < ( $this->rows - 1 ); ++$i ) + { + for ( $j = $i + 1; $j < $this->rows; ++$j ) + { + if ( $equatation->get( $j, $i ) !== 0 ) + { + if ( $equatation->get( $j, $i ) == 0 ) + { + continue; + } + else + { + $factor = -( $equatation->get( $i, $i ) / $equatation->get( $j, $i ) ); + } + + for ( $k = $i; $k < $columns; ++$k ) + { + $equatation->set( $j, $k, $equatation->get( $i, $k ) + $factor * $equatation->get( $j, $k ) ); + } + } + } + } + + // Normalize values on left side matrix diagonale + for ( $i = 0; $i < $this->rows; ++$i ) + { + if ( ( ( $value = $equatation->get( $i, $i ) ) != 1 ) && + ( $value != 0 ) ) + { + $factor = 1 / $value; + for ( $k = $i; $k < $columns; ++$k ) + { + $equatation->set( $i, $k, $equatation->get( $i, $k ) * $factor ); + } + } + } + + // Build up solving polynom + $polynom = new ezcGraphPolynom(); + for ( $i = ( $this->rows - 1 ); $i >= 0; --$i ) + { + for ( $j = $i + 1; $j < $this->columns; ++$j ) + { + $equatation->set( + $i, + $this->columns, + $equatation->get( $i, $this->columns ) + ( -$equatation->get( $i, $j ) * $polynom->get( $j ) ) + ); + $equatation->set( $i, $j, 0 ); + } + $polynom->set( $i, $equatation->get( $i, $this->columns ) ); + } + + return $polynom; + } + + /** + * Build LR decomposition from matrix + * + * Use Cholesky-Crout algorithm to get LR decomposition of the current + * matrix. + * + * Will return an array with two matrices: + * array( + * 'l' => (ezcGraphMatrix) $left, + * 'r' => (ezcGraphMatrix) $right, + * ) + * + * @return array( ezcGraphMatrix ) + */ + public function LRdecomposition() + { + /** + * Use Cholesky-Crout algorithm to get LR decomposition + * + * Input: Matrix A ($this) + * + * For i = 1 To n + * For j = i To n + * R(i,j)=A(i,j) + * For k = 1 TO i-1 + * R(i,j)-=L(i,k)*R(k,j) + * end + * end + * For j=i+1 To n + * L(j,i)= A(j,i) + * For k = 1 TO i-1 + * L(j,i)-=L(j,k)*R(k,i) + * end + * L(j,i)/=R(i,i) + * end + * end + * + * Output: matrices L,R + */ + $l = new ezcGraphMatrix( $this->columns, $this->rows ); + $r = new ezcGraphMatrix( $this->columns, $this->rows ); + + for ( $i = 0; $i < $this->columns; ++$i ) + { + for ( $j = $i; $j < $this->rows; ++$j ) + { + $r->set( $i, $j, $this->matrix[$i][$j] ); + for ( $k = 0; $k <= ( $i - 1 ); ++$k ) + { + $r->set( $i, $j, $r->get( $i, $j ) - $l->get( $i, $k ) * $r->get( $k, $j ) ); + } + } + + for ( $j = $i + 1; $j < $this->rows; ++$j ) + { + $l->set( $j, $i, $this->matrix[$j][$i] ); + for ( $k = 0; $k <= ( $i - 1 ); ++$k ) + { + $l->set( $j, $i, $l->get( $j, $i ) - $l->get( $j, $k ) * $r->get( $k, $i ) ); + } + $l->set( $j, $i, $l->get( $j, $i ) / $r->get( $i, $i ) ); + } + } + + return array( + 'l' => $l, + 'r' => $r, + ); + } + + /** + * Returns a string representation of the matrix + * + * @return string + */ + public function __toString() + { + $string = sprintf( "%d x %d matrix:\n", $this->rows, $this->columns ); + + for ( $i = 0; $i < $this->rows; ++$i ) + { + $string .= '| '; + for ( $j = 0; $j < $this->columns; ++$j ) + { + $string .= sprintf( '%04.2f ', $this->get( $i, $j ) ); + } + $string .= "|\n"; + } + + return $string; + } +} +?> diff --git a/include/ezcomponents/Graph/src/math/polynom.php b/include/ezcomponents/Graph/src/math/polynom.php new file mode 100644 index 000000000..c2f0484aa --- /dev/null +++ b/include/ezcomponents/Graph/src/math/polynom.php @@ -0,0 +1,230 @@ + (float) 2, + * (int) 1 => (float) .5, + * (int) 0 => (float) -3, + * ) + * + * @var array + */ + protected $values; + + // @TODO: Introduce precision option for string output? + + /** + * Constructor + * + * Constructs a polynom object from given array, where the key is the + * exponent and the value the factor. + * An example: + * Polynom: + * 2 * x^3 + .5 * x - 3 + * Array: + * array ( + * (int) 3 => (float) 2, + * (int) 1 => (float) .5, + * (int) 0 => (float) -3, + * ) + * + * @param array $values Array with values + * @return ezcGraphPolynom + */ + public function __construct( array $values = array() ) + { + foreach ( $values as $exponent => $factor ) + { + $this->values[(int) $exponent] = (float) $factor; + } + } + + /** + * Initialise a polygon + * + * Initialise a polygon of the given order. Sets all factors to 0. + * + * @param int $order Order of polygon + * @return ezcGraphPolynom Created polynom + */ + public function init( $order ) + { + for ( $i = 0; $i <= $order; ++$i ) + { + $this->values[$i] = 0; + } + + return $this; + } + + /** + * Return factor for one exponent + * + * @param int $exponent Exponent + * @return float Factor + */ + public function get( $exponent ) + { + if ( !isset( $this->values[$exponent] ) ) + { + return 0; + } + else + { + return $this->values[$exponent]; + } + } + + /** + * Set the factor for one exponent + * + * @param int $exponent Exponent + * @param float $factor Factor + * @return ezcGraphPolynom Modified polynom + */ + public function set( $exponent, $factor ) + { + $this->values[(int) $exponent] = (float) $factor; + + return $this; + } + + /** + * Returns the order of the polynom + * + * @return int Polynom order + */ + public function getOrder() + { + return max( array_keys( $this->values ) ); + } + + /** + * Adds polynom to current polynom + * + * @param ezcGraphPolynom $polynom Polynom to add + * @return ezcGraphPolynom Modified polynom + */ + public function add( ezcGraphPolynom $polynom ) + { + $order = max( + $this->getOrder(), + $polynom->getOrder() + ); + + for ( $i = 0; $i <= $order; ++$i ) + { + $this->set( $i, $this->get( $i ) + $polynom->get( $i ) ); + } + + return $this; + } + + /** + * Evaluate Polynom with a given value + * + * @param float $x Value + * @return float Result + */ + public function evaluate( $x ) + { + $value = 0; + foreach ( $this->values as $exponent => $factor ) + { + $value += $factor * pow( $x, $exponent ); + } + + return $value; + } + + /** + * Returns a string represenation of the polynom + * + * @return string String representation of polynom + */ + public function __toString() + { + krsort( $this->values ); + $string = ''; + + foreach ( $this->values as $exponent => $factor ) + { + if ( $factor == 0 ) + { + continue; + } + + $string .= ( $factor < 0 ? ' - ' : ' + ' ); + + $factor = abs( $factor ); + switch ( true ) + { + case abs( 1 - $factor ) < .0001: + // No not append, if factor is ~1 + break; + case $factor < 1: + case $factor >= 1000: + $string .= sprintf( '%.2e ', $factor ); + break; + case $factor >= 100: + $string .= sprintf( '%.0f ', $factor ); + break; + case $factor >= 10: + $string .= sprintf( '%.1f ', $factor ); + break; + default: + $string .= sprintf( '%.2f ', $factor ); + break; + } + + switch ( true ) + { + case $exponent > 1: + $string .= sprintf( 'x^%d', $exponent ); + break; + case $exponent === 1: + $string .= 'x'; + break; + case $exponent === 0: + if ( abs( 1 - $factor ) < .0001 ) + { + $string .= '1'; + } + break; + } + } + + if ( substr( $string, 0, 3 ) === ' + ' ) + { + $string = substr( $string, 3 ); + } + else + { + $string = '-' . substr( $string, 3 ); + } + + return trim( $string ); + } +} +?> diff --git a/include/ezcomponents/Graph/src/math/rotation.php b/include/ezcomponents/Graph/src/math/rotation.php new file mode 100644 index 000000000..f17467ca0 --- /dev/null +++ b/include/ezcomponents/Graph/src/math/rotation.php @@ -0,0 +1,90 @@ +rotation = (float) $rotation; + + if ( $center === null ) + { + $this->center = new ezcGraphCoordinate( 0, 0 ); + + $clockwiseRotation = deg2rad( $rotation ); + $rotationMatrixArray = array( + array( cos( $clockwiseRotation ), -sin( $clockwiseRotation ), 0 ), + array( sin( $clockwiseRotation ), cos( $clockwiseRotation ), 0 ), + array( 0, 0, 1 ), + ); + + return parent::__construct( $rotationMatrixArray ); + } + + parent::__construct(); + + $this->center = $center; + + $this->multiply( new ezcGraphTranslation( $center->x, $center->y ) ); + $this->multiply( new ezcGraphRotation( $rotation ) ); + $this->multiply( new ezcGraphTranslation( -$center->x, -$center->y ) ); + } + + /** + * Return rotaion angle in degrees + * + * @return float + */ + public function getRotation() + { + return $this->rotation; + } + + /** + * Return the center point of the current rotation + * + * @return ezcGraphCoordinate + */ + public function getCenter() + { + return $this->center; + } +} + +?> diff --git a/include/ezcomponents/Graph/src/math/transformation.php b/include/ezcomponents/Graph/src/math/transformation.php new file mode 100644 index 000000000..3731a4847 --- /dev/null +++ b/include/ezcomponents/Graph/src/math/transformation.php @@ -0,0 +1,85 @@ +columns(); + + // We want to ensure, that the matrix stays 3x3 + if ( ( $this->columns !== $matrix->rows() ) && + ( $this->rows !== $mColumns ) ) + { + throw new ezcGraphMatrixInvalidDimensionsException( $this->columns, $this->rows, $mColumns, $matrix->rows() ); + } + + $result = parent::multiply( $matrix ); + + // The matrix dimensions stay the same, so that we can modify $this. + for ( $i = 0; $i < $this->rows; ++$i ) + { + for ( $j = 0; $j < $mColumns; ++$j ) + { + $this->set( $i, $j, $result->get( $i, $j ) ); + } + } + + return $this; + } + + /** + * Transform a coordinate with the current transformation matrix. + * + * @param ezcGraphCoordinate $coordinate + * @return ezcGraphCoordinate + */ + public function transformCoordinate( ezcGraphCoordinate $coordinate ) + { + $vector = new ezcGraphMatrix( 3, 1, array( array( $coordinate->x ), array( $coordinate->y ), array( 1 ) ) ); + $vector = parent::multiply( $vector ); + + return new ezcGraphCoordinate( $vector->get( 0, 0 ), $vector->get( 1, 0 ) ); + } +} + +?> diff --git a/include/ezcomponents/Graph/src/math/translation.php b/include/ezcomponents/Graph/src/math/translation.php new file mode 100644 index 000000000..1304b9067 --- /dev/null +++ b/include/ezcomponents/Graph/src/math/translation.php @@ -0,0 +1,38 @@ + diff --git a/include/ezcomponents/Graph/src/math/vector.php b/include/ezcomponents/Graph/src/math/vector.php new file mode 100644 index 000000000..28edd6cd2 --- /dev/null +++ b/include/ezcomponents/Graph/src/math/vector.php @@ -0,0 +1,187 @@ +x; + $this->x = $this->y; + $this->y = -$tmp; + + return $this; + } + + /** + * Rotates vector to the right by 90 degrees + * + * @return void + */ + public function rotateClockwise() + { + $tmp = $this->x; + $this->x = -$this->y; + $this->y = $tmp; + + return $this; + } + + /** + * Unifies vector length to 1 + * + * @return void + */ + public function unify() + { + $length = $this->length(); + if ( $length == 0 ) + { + return $this; + } + + $this->x /= $length; + $this->y /= $length; + + return $this; + } + + /** + * Returns length of vector + * + * @return float + */ + public function length() + { + return sqrt( + pow( $this->x, 2 ) + + pow( $this->y, 2 ) + ); + } + + /** + * Multiplies vector with a scalar + * + * @param float $value + * @return void + */ + public function scalar( $value ) + { + $this->x *= $value; + $this->y *= $value; + + return $this; + } + + /** + * Calculates scalar product of two vectors + * + * @param ezcGraphCoordinate $vector + * @return void + */ + public function mul( ezcGraphCoordinate $vector ) + { + return $this->x * $vector->x + $this->y * $vector->y; + } + + /** + * Returns the angle between two vectors in radian + * + * @param ezcGraphCoordinate $vector + * @return float + */ + public function angle( ezcGraphCoordinate $vector ) + { + if ( !$vector instanceof ezcGraphVector ) + { + // Ensure beeing a vector for calling length() + $vector = ezcGraphVector::fromCoordinate( $vector ); + } + + $factor = $this->length() * $vector->length(); + + if ( $factor == 0 ) + { + return false; + } + else + { + return acos( $this->mul( $vector ) / $factor ); + } + } + + /** + * Adds a vector to another vector + * + * @param ezcGraphCoordinate $vector + * @return void + */ + public function add( ezcGraphCoordinate $vector ) + { + $this->x += $vector->x; + $this->y += $vector->y; + + return $this; + } + + /** + * Subtracts a vector from another vector + * + * @param ezcGraphCoordinate $vector + * @return void + */ + public function sub( ezcGraphCoordinate $vector ) + { + $this->x -= $vector->x; + $this->y -= $vector->y; + + return $this; + } + + /** + * Creates a vector from a coordinate object + * + * @param ezcGraphCoordinate $coordinate + * @return ezcGraphVector + */ + public static function fromCoordinate( ezcGraphCoordinate $coordinate ) + { + return new ezcGraphVector( $coordinate->x, $coordinate->y ); + } + + /** + * Transform vector using transformation matrix + * + * @param ezcGraphTransformation $transformation + * @return ezcGraphVector + */ + public function transform( ezcGraphTransformation $transformation ) + { + $result = $transformation->transformCoordinate( $this ); + + $this->x = $result->x; + $this->y = $result->y; + + return $this; + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/cairo_driver.php b/include/ezcomponents/Graph/src/options/cairo_driver.php new file mode 100644 index 000000000..d9d966972 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/cairo_driver.php @@ -0,0 +1,100 @@ + + * $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->driver = new ezcGraphCairoDriver(); + * + * // No options yet. + * + * $graph->render( 400, 200, 'tutorial_driver_cairo.png' ); + * + * + * @property float $imageMapResolution + * Degree step used to interpolate round image primitives by + * polygons for image maps + * @property float $circleResolution + * Resolution for circles, until I understand how to draw ellipses + * with SWFShape::curveTo() + * + * @version 1.3 + * @package Graph + */ +class ezcGraphCairoDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['imageMapResolution'] = 10; + $this->properties['circleResolution'] = 2.; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'imageMapResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['imageMapResolution'] = (int) $propertyValue; + break; + case 'circleResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['circleResolution'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/chart.php b/include/ezcomponents/Graph/src/options/chart.php new file mode 100644 index 000000000..311da801f --- /dev/null +++ b/include/ezcomponents/Graph/src/options/chart.php @@ -0,0 +1,107 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteEzBlue(); + * $graph->title = 'Access statistics'; + * + * // Global font options + * $graph->options->font->name = 'serif'; + * + * // Special font options for sub elements + * $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' ); + * + * + * @property int $width + * Width of the chart. + * @property int $height + * Height of the chart. + * @property ezcGraphFontOptions $font + * Font used in the graph. + * + * @version 1.3 + * @package Graph + */ +class ezcGraphChartOptions extends ezcBaseOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['width'] = null; + $this->properties['height'] = null; + $this->properties['font'] = new ezcGraphFontOptions(); + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'width': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['width'] = (int) $propertyValue; + break; + case 'height': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['height'] = (int) $propertyValue; + break; + case 'font': + $this->properties['font']->path = $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/driver.php b/include/ezcomponents/Graph/src/options/driver.php new file mode 100644 index 000000000..1711f6716 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/driver.php @@ -0,0 +1,157 @@ + + * require_once 'tutorial_autoload.php'; + * + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteEzBlue(); + * $graph->title = 'Access statistics'; + * + * $graph->data['Access statistics'] = new ezcGraphArrayDataSet( array( + * 'Mozilla' => 19113, + * 'Explorer' => 10917, + * 'Opera' => 1464, + * 'Safari' => 652, + * 'Konqueror' => 474, + * ) ); + * + * $graph->driver->options->autoShortenString = false; + * + * $graph->render( 400, 150, 'tutorial_chart_title.svg' ); + * + * + * @property int $width + * Width of the chart. + * @property int $height + * Height of the chart. + * @property float $shadeCircularArc + * Percent to darken circular arcs at the sides + * @property float $lineSpacing + * Percent of font size used for line spacing + * @property int $font + * Font used in the graph. + * @property bool $autoShortenString + * Automatically shorten string if it does not fit into a box + * @property string $autoShortenStringPostFix + * String to append to shortened strings, if there is enough space + * + * @version 1.3 + * @package Graph + */ +abstract class ezcGraphDriverOptions extends ezcBaseOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['width'] = null; + $this->properties['height'] = null; + + $this->properties['lineSpacing'] = .1; + $this->properties['shadeCircularArc'] = .5; + $this->properties['font'] = new ezcGraphFontOptions(); + $this->properties['font']->color = ezcGraphColor::fromHex( '#000000' ); + + $this->properties['autoShortenString'] = true; + $this->properties['autoShortenStringPostFix'] = '..'; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'width': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['width'] = (int) $propertyValue; + break; + case 'height': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['height'] = (int) $propertyValue; + break; + case 'lineSpacing': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['lineSpacing'] = (float) $propertyValue; + break; + case 'shadeCircularArc': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['shadeCircularArc'] = (float) $propertyValue; + break; + case 'font': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['font'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + case 'autoShortenString': + if ( is_bool( $propertyValue ) ) + { + $this->properties['autoShortenString'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'boolean' ); + } + break; + case 'autoShortenStringPostFix': + $this->properties['autoShortenStringPostFix'] = (string) $propertyValue; + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + break; + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/flash_driver.php b/include/ezcomponents/Graph/src/options/flash_driver.php new file mode 100644 index 000000000..c49565290 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/flash_driver.php @@ -0,0 +1,103 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->driver = new ezcGraphFlashDriver(); + * $graph->driver->options->compresion = 0; + * + * $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' ); + * + * + * @property int $compression + * Compression level used for generated flash file + * @see http://php.net/manual/en/function.swfmovie.save.php + * @property float $circleResolution + * Resolution for circles, until I understand how to draw ellipses + * with SWFShape::curveTo() + * + * @version 1.3 + * @package Graph + */ +class ezcGraphFlashDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['compression'] = 9; + $this->properties['circleResolution'] = 2.; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'compression': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 9 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 9' ); + } + + $this->properties['compression'] = max( 0, min( 9, (int) $propertyValue ) ); + break; + case 'circleResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['circleResolution'] = (float) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/font.php b/include/ezcomponents/Graph/src/options/font.php new file mode 100644 index 000000000..67dffc2d3 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/font.php @@ -0,0 +1,292 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteEzBlue(); + * $graph->title = 'Access statistics'; + * + * $graph->options->font->name = 'serif'; + * $graph->options->font->maxFontSize = 12; + * + * $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' ); + * + * + * @property string $name + * Name of font. + * @property string $path + * Path to font file. + * @property int $type + * Type of used font. May be one of the following: + * - TTF_FONT Native TTF fonts + * - PS_FONT PostScript Type1 fonts + * - FT2_FONT FreeType 2 fonts + * @property float $minFontSize + * Minimum font size for displayed texts. + * @property float $maxFontSize + * Maximum font size for displayed texts. + * @property float $minimalUsedFont + * The minimal used font size for this element. + * @property ezcGraphColor $color + * Font color. + * @property ezcGraphColor $background + * Background color + * @property ezcGraphColor $border + * Border color + * @property int $borderWidth + * Border width + * @property int $padding + * Padding between text and border + * @property bool $minimizeBorder + * Fit the border exactly around the text, or use the complete + * possible space. + * @property bool $textShadow + * Draw shadow for texts + * @property int $textShadowOffset + * Offset for text shadow + * @property ezcGraphColor $textShadowColor + * Color of text shadow. If false the inverse color of the text + * color will be used. + * + * @version 1.3 + * @package Graph + */ +class ezcGraphFontOptions extends ezcBaseOptions +{ + /** + * Indicates if path already has been checked for correct font + * + * @var bool + */ + protected $pathChecked = false; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['name'] = 'sans-serif'; +// $this->properties['path'] = 'Graph/tests/data/font.ttf'; + $this->properties['path'] = ''; + $this->properties['type'] = ezcGraph::TTF_FONT; + + $this->properties['minFontSize'] = 6; + $this->properties['maxFontSize'] = 96; + $this->properties['minimalUsedFont'] = 96; + $this->properties['color'] = ezcGraphColor::fromHex( '#000000' ); + + $this->properties['background'] = false; + $this->properties['border'] = false; + $this->properties['borderWidth'] = 1; + $this->properties['padding'] = 0; + $this->properties['minimizeBorder'] = true; + + $this->properties['textShadow'] = false; + $this->properties['textShadowOffset'] = 1; + $this->properties['textShadowColor'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'minFontSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 1' ); + } + + // Ensure min font size is smaller or equal max font size. + if ( $propertyValue > $this->properties['maxFontSize'] ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float <= ' . $this->properties['maxFontSize'] ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'maxFontSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 1' ); + } + + // Ensure max font size is greater or equal min font size. + if ( $propertyValue < $this->properties['minFontSize'] ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float >= ' . $this->properties['minFontSize'] ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'minimalUsedFont': + $propertyValue = (float) $propertyValue; + if ( $propertyValue < $this->minimalUsedFont ) + { + $this->properties['minimalUsedFont'] = $propertyValue; + } + break; + + case 'color': + case 'background': + case 'border': + case 'textShadowColor': + $this->properties[$propertyName] = ezcGraphColor::create( $propertyValue ); + break; + + case 'borderWidth': + case 'padding': + case 'textShadowOffset': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + + case 'minimizeBorder': + case 'textShadow': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + $this->properties[$propertyName] = (bool) $propertyValue; + break; + + case 'name': + if ( is_string( $propertyValue ) ) + { + $this->properties['name'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'string' ); + } + break; + case 'path': + if ( is_file( $propertyValue ) && is_readable( $propertyValue ) ) + { + $this->properties['path'] = realpath( $propertyValue ); + $parts = pathinfo( $this->properties['path'] ); + switch ( strtolower( $parts['extension'] ) ) + { + case 'fdb': + $this->properties['type'] = ezcGraph::PALM_FONT; + break; + case 'pfb': + $this->properties['type'] = ezcGraph::PS_FONT; + break; + case 'ttf': + $this->properties['type'] = ezcGraph::TTF_FONT; + break; + case 'svg': + $this->properties['type'] = ezcGraph::SVG_FONT; + $this->properties['name'] = ezcGraphSvgFont::getFontName( $propertyValue ); + break; + default: + throw new ezcGraphUnknownFontTypeException( $propertyValue, $parts['extension'] ); + } + $this->pathChecked = true; + } + else + { + throw new ezcBaseFileNotFoundException( $propertyValue, 'font' ); + } + break; + case 'type': + if ( is_int( $propertyValue ) ) + { + $this->properties['type'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int' ); + } + break; + default: + throw new ezcBasePropertyNotFoundException( $propertyName ); + 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 'textShadowColor': + // Use inverted font color if false + if ( $this->properties['textShadowColor'] === false ) + { + $this->properties['textShadowColor'] = $this->properties['color']->invert(); + } + + return $this->properties['textShadowColor']; + case 'path': + if ( $this->pathChecked === false ) + { + // Enforce call of path check + $this->__set( 'path', $this->properties['path'] ); + } + // No break to use parent return + default: + return parent::__get( $propertyName ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/gd_driver.php b/include/ezcomponents/Graph/src/options/gd_driver.php new file mode 100644 index 000000000..9565d6d52 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/gd_driver.php @@ -0,0 +1,181 @@ + + * $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 better + * // quality image + * $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' ); + * + * + * @property int $imageFormat + * Type of generated image. + * Should be one of those: IMG_PNG, IMG_JPEG + * @property int $jpegQuality + * Quality of generated jpeg + * @property int $detail + * Count of degrees to render one polygon for in circular arcs + * @property int $supersampling + * Factor of supersampling used to simulate antialiasing + * @property string $background + * Background image to put the graph on + * @property string $resampleFunction + * Function used to resample / resize images + * @property bool $forceNativeTTF + * Force use of native ttf functions instead of free type 2 + * @property float $imageMapResolution + * Degree step used to interpolate round image primitives by + * polygons for image maps + * + * @version 1.3 + * @package Graph + */ +class ezcGraphGdDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['imageFormat'] = IMG_PNG; + $this->properties['jpegQuality'] = 70; + $this->properties['detail'] = 1; + $this->properties['shadeCircularArc'] = .5; + $this->properties['supersampling'] = 2; + $this->properties['background'] = false; + $this->properties['resampleFunction'] = 'imagecopyresampled'; + $this->properties['forceNativeTTF'] = false; + $this->properties['imageMapResolution'] = 10; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'imageFormat': + if ( imagetypes() & $propertyValue ) + { + $this->properties['imageFormat'] = (int) $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'Unsupported image type.' ); + } + break; + case 'jpegQuality': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 100 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= int <= 100' ); + } + + $this->properties['jpegQuality'] = (int) $propertyValue; + break; + case 'detail': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['detail'] = (int) $propertyValue; + break; + case 'supersampling': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['supersampling'] = (int) $propertyValue; + break; + case 'background': + if ( $propertyValue === false || + ( is_file( $propertyValue ) && is_readable( $propertyValue ) ) ) + { + $this->properties['background'] = realpath( $propertyValue ); + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'readable file' ); + } + break; + case 'resampleFunction': + if ( ezcBaseFeatures::hasFunction( $propertyValue ) ) + { + $this->properties['resampleFunction'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'function' ); + } + break; + case 'forceNativeTTF': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['forceNativeTTF'] = (bool) $propertyValue; + break; + case 'imageMapResolution': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties['imageMapResolution'] = (int) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/line_chart.php b/include/ezcomponents/Graph/src/options/line_chart.php new file mode 100644 index 000000000..a51de57f1 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/line_chart.php @@ -0,0 +1,189 @@ + + * $graph = new ezcGraphLineChart(); + * $graph->title = 'Wikipedia articles'; + * + * $graph->options->fillLines = 220; + * $graph->options->lineThickness = 3; + * + * // Add data + * foreach ( $wikidata as $language => $data ) + * { + * $graph->data[$language] = new ezcGraphArrayDataSet( $data ); + * } + * + * $graph->render( 400, 150, 'tutorial_line_chart.svg' ); + * + * + * @property float $lineThickness + * Thickness of chart lines + * @property mixed $fillLines + * Status wheather the space between line and axis should get filled. + * - FALSE to not fill the space at all. + * - (int) Opacity used to fill up the space with the lines color. + * @property int $symbolSize + * Size of symbols in line chart. + * @property ezcGraphFontOptions $highlightFont + * Font configuration for highlight tests + * @property int $highlightSize + * Size of highlight blocks + * @property bool $highlightLines + * If true, it adds lines to highlight the values position on the + * axis. + * @property true $stackBars + * Stack bars + * + * @version 1.3 + * @package Graph + */ +class ezcGraphLineChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['lineThickness'] = 1; + $this->properties['fillLines'] = false; + $this->properties['symbolSize'] = 8; + $this->properties['highlightFont'] = new ezcGraphFontOptions(); + $this->properties['highlightFontCloned'] = false; + $this->properties['highlightSize'] = 14; + $this->properties['highlightLines'] = false; + $this->properties['stackBars'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'lineThickness': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + case 'symbolSize': + case 'highlightSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + case 'fillLines': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 255 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= int <= 255' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (int) $propertyValue ); + break; + case 'highlightFont': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['highlightFont'] = $propertyValue; + } + elseif ( is_string( $propertyValue ) ) + { + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->font; + $this->properties['highlightFontCloned'] = true; + } + + $this->properties['highlightFont']->path = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + $this->properties['highlightSize'] = max( 1, (int) $propertyValue ); + break; + case 'highlightLines': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['highlightLines'] = $propertyValue; + break; + case 'stackBars': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['stackBars'] = $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * __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 'highlightFont': + // Clone font configuration when requested for this element + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->properties['font']; + $this->properties['highlightFontCloned'] = true; + } + return $this->properties['highlightFont']; + default: + return parent::__get( $propertyName ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/odometer_chart.php b/include/ezcomponents/Graph/src/options/odometer_chart.php new file mode 100644 index 000000000..83db33453 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/odometer_chart.php @@ -0,0 +1,113 @@ + + * $graph = new ezcGraphOdoMeterChart(); + * + * $graph->data['Test'] = new ezcGraphArrayDataSet( array( 0, 1, 23, 30 ) ); + * + * $graph->options->odometerHeight = .3; + * $graph->options->borderColor = '#2e3436'; + * + * $graph->render( 150, 50, 'odometer.svg' ); + * + * + * @property ezcGraphColor $borderColor + * Color of border around odometer chart + * @property int $borderWidth + * Width of border around odometer chart + * @property ezcGraphColor $startColor + * Start color of grdient used as the odometer chart background. + * @property ezcGraphColor $endColor + * End color of grdient used as the odometer chart background. + * @property int $markerWidth + * Width of odometer markers + * @property float $odometerHeight + * Height consumed by odometer chart + * + * @version 1.3 + * @package Graph + */ +class ezcGraphOdometerChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['borderColor'] = ezcGraphColor::create( '#000000' ); + $this->properties['borderWidth'] = 0; + + $this->properties['startColor'] = ezcGraphColor::create( '#4e9a06A0' ); + $this->properties['endColor'] = ezcGraphColor::create( '#A40000A0' ); + + $this->properties['markerWidth'] = 2; + + $this->properties['odometerHeight'] = 0.5; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'borderWidth': + case 'markerWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + + case 'borderColor': + case 'startColor': + case 'endColor': + $this->properties[$propertyName] = ezcGraphColor::create( $propertyValue ); + break; + + case 'odometerHeight': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/pie_chart.php b/include/ezcomponents/Graph/src/options/pie_chart.php new file mode 100644 index 000000000..c4e3f7ab8 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/pie_chart.php @@ -0,0 +1,143 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->palette = new ezcGraphPaletteEzRed(); + * $graph->title = 'Access statistics'; + * $graph->legend = false; + * + * $graph->options->label = '%1$s (%3$.1f)'; + * $graph->options->percentThreshold = .05; + * + * $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->render( 400, 150, 'tutorial_pie_chart_options.svg' ); + * + * + * @property string $label + * String used to label pies + * %1$s Name of pie + * %2$d Value of pie + * %3$.1f Percentage + * @property callback $labelCallback + * Callback function to format pie chart labels. + * Function will receive 3 parameters: + * string function( label, value, percent ) + * @property float $sum + * Fixed sum of values. This should be used for incomplete pie + * charts. + * @property float $percentThreshold + * Values with a lower percentage value are aggregated. + * @property float $absoluteThreshold + * Values with a lower absolute value are aggregated. + * @property string $summarizeCaption + * Caption for values summarized because they are lower then the + * configured tresh hold. + * + * @version 1.3 + * @package Graph + */ +class ezcGraphPieChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['label'] = '%1$s: %2$d (%3$.1f%%)'; + $this->properties['labelCallback'] = null; + $this->properties['sum'] = false; + + $this->properties['percentThreshold'] = .0; + $this->properties['absoluteThreshold'] = .0; + $this->properties['summarizeCaption'] = 'Misc'; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'label': + $this->properties['label'] = (string) $propertyValue; + break; + case 'labelCallback': + if ( is_callable( $propertyValue ) ) + { + $this->properties['labelCallback'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'callback function' ); + } + break; + case 'sum': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['sum'] = (float) $propertyValue; + break; + case 'percentThreshold': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['percentThreshold'] = (float) $propertyValue; + break; + case 'absoluteThreshold': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['absoluteThreshold'] = (float) $propertyValue; + break; + case 'summarizeCaption': + $this->properties['summarizeCaption'] = (string) $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/radar_chart.php b/include/ezcomponents/Graph/src/options/radar_chart.php new file mode 100644 index 000000000..0a2b1cb29 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/radar_chart.php @@ -0,0 +1,172 @@ + + * $wikidata = include 'tutorial_wikipedia_data.php'; + * + * $graph = new ezcGraphRadarChart(); + * $graph->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' ); + * + * + * @property float $lineThickness + * Theickness of chart lines + * @property mixed $fillLines + * Status wheather the space between line and axis should get filled. + * - FALSE to not fill the space at all. + * - (int) Opacity used to fill up the space with the lines color. + * @property int $symbolSize + * Size of symbols in line chart. + * @property ezcGraphFontOptions $highlightFont + * Font configuration for highlight tests + * @property int $highlightSize + * Size of highlight blocks + * @property bool $highlightRadars + * If true, it adds lines to highlight the values position on the + * axis. + * + * @version 1.3 + * @package Graph + */ +class ezcGraphRadarChartOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['lineThickness'] = 1; + $this->properties['fillLines'] = false; + $this->properties['symbolSize'] = 8; + $this->properties['highlightFont'] = new ezcGraphFontOptions(); + $this->properties['highlightFontCloned'] = false; + $this->properties['highlightSize'] = 14; + $this->properties['highlightRadars'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'lineThickness': + case 'symbolSize': + case 'highlightSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 1' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + case 'fillLines': + if ( ( $propertyValue !== false ) && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 255 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= int <= 255' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (int) $propertyValue ); + break; + case 'highlightFont': + if ( $propertyValue instanceof ezcGraphFontOptions ) + { + $this->properties['highlightFont'] = $propertyValue; + } + elseif ( is_string( $propertyValue ) ) + { + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->font; + $this->properties['highlightFontCloned'] = true; + } + + $this->properties['highlightFont']->path = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphFontOptions' ); + } + break; + $this->properties['highlightSize'] = max( 1, (int) $propertyValue ); + break; + case 'highlightRadars': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['highlightRadars'] = $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * __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 'highlightFont': + // Clone font configuration when requested for this element + if ( !$this->properties['highlightFontCloned'] ) + { + $this->properties['highlightFont'] = clone $this->properties['font']; + $this->properties['highlightFontCloned'] = true; + } + return $this->properties['highlightFont']; + default: + return parent::__get( $propertyName ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/renderer.php b/include/ezcomponents/Graph/src/options/renderer.php new file mode 100644 index 000000000..7e657a181 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/renderer.php @@ -0,0 +1,212 @@ + + * $wikidata = include 'tutorial_wikipedia_data.php'; + * + * $graph = new ezcGraphBarChart(); + * $graph->title = 'Wikipedia articles'; + * + * // Add data + * foreach ( $wikidata as $language => $data ) + * { + * $graph->data[$language] = new ezcGraphArrayDataSet( $data ); + * } + * + * // $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' ); + * + * + * @property float $maxLabelHeight + * Percent of chart height used as maximum height for pie chart + * labels. + * @property bool $showSymbol + * Indicates wheather to show the line between pie elements and + * labels. + * @property float $symbolSize + * Size of symbols used to connect a label with a pie. + * @property float $moveOut + * Percent to move pie chart elements out of the middle on highlight. + * @property int $titlePosition + * Position of title in a box. + * @property int $titleAlignement + * Alignement of box titles. + * @property float $dataBorder + * Factor to darken border of data elements, like lines, bars and + * pie segments. + * @property float $barMargin + * Procentual distance between bar blocks. + * @property float $barPadding + * Procentual distance between bars. + * @property float $pieChartOffset + * Offset for starting with first pie chart segment in degrees. + * @property float $legendSymbolGleam + * Opacity of gleam in legend symbols + * @property float $legendSymbolGleamSize + * Size of gleam in legend symbols + * @property float $legendSymbolGleamColor + * Color of gleam in legend symbols + * @property float $pieVerticalSize + * Percent of vertical space used for maximum pie chart size. + * @property float $pieHorizontalSize + * Percent of horizontal space used for maximum pie chart size. + * @property float $pieChartSymbolColor + * Color of pie chart symbols + * @property float $pieChartGleam + * Enhance pie chart with gleam on top. + * @property float $pieChartGleamColor + * Color used for gleam on pie charts. + * @property float $pieChartGleamBorder + * Do not draw gleam on an outer border of this size. + * @property bool $syncAxisFonts + * Synchronize fonts of axis. With the defaut true value, the only + * the fonts of the yAxis will be used. + * + * @version 1.3 + * @package Graph + */ +class ezcGraphRendererOptions extends ezcGraphChartOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['maxLabelHeight'] = .10; + $this->properties['showSymbol'] = true; + $this->properties['symbolSize'] = 6; + $this->properties['moveOut'] = .1; + $this->properties['titlePosition'] = ezcGraph::TOP; + $this->properties['titleAlignement'] = ezcGraph::MIDDLE | ezcGraph::CENTER; + $this->properties['dataBorder'] = .5; + $this->properties['barMargin'] = .1; + $this->properties['barPadding'] = .05; + $this->properties['pieChartOffset'] = 0; + $this->properties['pieChartSymbolColor'] = ezcGraphColor::fromHex( '#000000' ); + $this->properties['pieChartGleam'] = false; + $this->properties['pieChartGleamColor'] = ezcGraphColor::fromHex( '#FFFFFF' ); + $this->properties['pieChartGleamBorder'] = 0; + $this->properties['legendSymbolGleam'] = false; + $this->properties['legendSymbolGleamSize'] = .9; + $this->properties['legendSymbolGleamColor'] = ezcGraphColor::fromHex( '#FFFFFF' ); + $this->properties['pieVerticalSize'] = .5; + $this->properties['pieHorizontalSize'] = .25; + $this->properties['syncAxisFonts'] = true; + + parent::__construct( $options ); + } + + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'dataBorder': + case 'pieChartGleam': + case 'legendSymbolGleam': + if ( $propertyValue !== false && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= float <= 1' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (float) $propertyValue ); + break; + + case 'maxLabelHeight': + case 'moveOut': + case 'barMargin': + case 'barPadding': + case 'legendSymbolGleamSize': + case 'pieVerticalSize': + case 'pieHorizontalSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'symbolSize': + case 'titlePosition': + case 'titleAlignement': + case 'pieChartGleamBorder': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'int >= 0' ); + } + + $this->properties[$propertyName] = (int) $propertyValue; + break; + + case 'showSymbol': + case 'syncAxisFonts': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + $this->properties[$propertyName] = (bool) $propertyValue; + break; + + case 'pieChartOffset': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 360 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 360' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'pieChartSymbolColor': + case 'pieChartGleamColor': + case 'legendSymbolGleamColor': + $this->properties[$propertyName] = ezcGraphColor::create( $propertyValue ); + break; + + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/renderer_2d.php b/include/ezcomponents/Graph/src/options/renderer_2d.php new file mode 100644 index 000000000..a005f980d --- /dev/null +++ b/include/ezcomponents/Graph/src/options/renderer_2d.php @@ -0,0 +1,120 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->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' ); + * + * + * @property int $pieChartShadowSize + * Size of shadows. + * @property float $pieChartShadowTransparency + * Used transparency for pie chart shadows. + * @property float $pieChartShadowColor + * Color used for pie chart shadows. + * + * @version 1.3 + * @package Graph + */ +class ezcGraphRenderer2dOptions extends ezcGraphRendererOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['pieChartShadowSize'] = 0; + $this->properties['pieChartShadowTransparency'] = .3; + $this->properties['pieChartShadowColor'] = ezcGraphColor::fromHex( '#000000' ); + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'pieChartShadowSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float >= 0' ); + } + + $this->properties['pieChartShadowSize'] = (int) $propertyValue; + break; + case 'pieChartShadowTransparency': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['pieChartShadowTransparency'] = (float) $propertyValue; + break; + case 'pieChartShadowColor': + $this->properties['pieChartShadowColor'] = ezcGraphColor::create( $propertyValue ); + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/renderer_3d.php b/include/ezcomponents/Graph/src/options/renderer_3d.php new file mode 100644 index 000000000..986196503 --- /dev/null +++ b/include/ezcomponents/Graph/src/options/renderer_3d.php @@ -0,0 +1,185 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->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' ); + * + * + * @property bool $seperateLines + * Indicates wheather the full depth should be used for each line in + * the chart, or beeing seperated by the count of lines. + * @property float $fillAxis + * Transparency used to fill the axis polygon. + * @property float $fillGrid + * Transparency used to fill the grid lines. + * @property float $depth + * Part of picture used to simulate depth of three dimensional chart. + * @property float $pieChartHeight + * Height of the pie charts border. + * @property float $pieChartRotation + * Rotation of pie chart. Defines the percent of width used to + * calculate the height of the ellipse. + * @property int $pieChartShadowSize + * Size of shadows. + * @property float $pieChartShadowTransparency + * Used transparency for pie chart shadows. + * @property float $pieChartShadowColor + * Color used for pie chart shadows. + * @property float $barDarkenSide + * Factor to darken the color used for the bars side polygon. + * @property float $barDarkenTop + * Factor to darken the color used for the bars top polygon. + * @property float $barChartGleam + * Transparancy for gleam on bar charts + * + * @version 1.3 + * @package Graph + */ +class ezcGraphRenderer3dOptions extends ezcGraphRendererOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['seperateLines'] = true; + $this->properties['fillAxis'] = .8; + $this->properties['fillGrid'] = 0; + $this->properties['depth'] = .1; + $this->properties['pieChartHeight'] = 10.; + $this->properties['pieChartRotation'] = .6; + $this->properties['pieChartShadowSize'] = 0; + $this->properties['pieChartShadowTransparency'] = .3; + $this->properties['pieChartShadowColor'] = ezcGraphColor::fromHex( '#000000' ); + $this->properties['pieChartGleam'] = false; + $this->properties['pieChartGleamColor'] = ezcGraphColor::fromHex( '#FFFFFF' ); + $this->properties['barDarkenSide'] = .2; + $this->properties['barDarkenTop'] = .4; + $this->properties['barChartGleam'] = false; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'fillAxis': + case 'fillGrid': + if ( $propertyValue !== false && + !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'false OR 0 <= float <= 1' ); + } + + $this->properties[$propertyName] = ( + $propertyValue === false + ? false + : (float) $propertyValue ); + break; + + case 'depth': + case 'pieChartRotation': + case 'pieChartShadowTransparency': + case 'barDarkenSide': + case 'barDarkenTop': + case 'barChartGleam': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'pieChartHeight': + case 'pieChartShadowSize': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue <= 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties[$propertyName] = (float) $propertyValue; + break; + + case 'seperateLines': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['seperateLines'] = $propertyValue; + break; + case 'pieChartShadowColor': + $this->properties['pieChartShadowColor'] = ezcGraphColor::create( $propertyValue ); + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/options/svg_driver.php b/include/ezcomponents/Graph/src/options/svg_driver.php new file mode 100644 index 000000000..8fdcca73e --- /dev/null +++ b/include/ezcomponents/Graph/src/options/svg_driver.php @@ -0,0 +1,272 @@ + + * $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->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' ); + * + * + * @property string $encoding + * Encoding of the SVG XML document + * @property float $assumedNumericCharacterWidth + * Assumed percentual average width of chars in numeric strings with + * the used font. + * @property float $assumedTextCharacterWidth + * Assumed percentual average width of chars in non numeric strings + * with the used font. + * @property string $strokeLineCap + * This specifies the shape to be used at the end of open subpaths + * when they are stroked. + * @property string $strokeLineJoin + * This specifies the shape to be used at the edges of paths. + * @property string $shapeRendering + * "The creator of SVG content might want to provide a hint to the + * implementation about what tradeoffs to make as it renders vector + * graphics elements such as 'path' elements and basic shapes such as + * circles and rectangles." + * @property string $colorRendering + * "The creator of SVG content might want to provide a hint to the + * implementation about how to make speed vs. quality tradeoffs as it + * performs color interpolation and compositing. The + * 'color-rendering' property provides a hint to the SVG user agent + * about how to optimize its color interpolation and compositing + * operations." + * @property string $textRendering + * "The creator of SVG content might want to provide a hint to the + * implementation about what tradeoffs to make as it renders text." + * @property mixed $templateDocument + * Use existing SVG document as template to insert graph into. If + * insertIntoGroup is not set, a new group will be inserted in the + * svg root node. + * @property mixed $insertIntoGroup + * ID of a SVG group node to insert the graph. Only works with a + * custom template document. + * @property ezcGraphCoordinate $graphOffset + * Offset of the graph in the svg. + * @property string $idPrefix + * Prefix used for the ids in SVG documents. + * @property string $linkCursor + * CSS value for cursor property used for linked SVG elements + * + * @version 1.3 + * @package Graph + */ +class ezcGraphSvgDriverOptions extends ezcGraphDriverOptions +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['encoding'] = null; + $this->properties['assumedNumericCharacterWidth'] = .62; + $this->properties['assumedTextCharacterWidth'] = .53; + $this->properties['strokeLineJoin'] = 'round'; + $this->properties['strokeLineCap'] = 'round'; + $this->properties['shapeRendering'] = 'geometricPrecision'; + $this->properties['colorRendering'] = 'optimizeQuality'; + $this->properties['textRendering'] = 'optimizeLegibility'; + $this->properties['templateDocument'] = false; + $this->properties['insertIntoGroup'] = false; + $this->properties['graphOffset'] = new ezcGraphCoordinate( 0, 0 ); + $this->properties['idPrefix'] = 'ezcGraph'; + $this->properties['linkCursor'] = 'pointer'; + + parent::__construct( $options ); + } + + /** + * Set an option value + * + * @param string $propertyName + * @param mixed $propertyValue + * @throws ezcBasePropertyNotFoundException + * If a property is not defined in this class + * @return void + * @ignore + */ + public function __set( $propertyName, $propertyValue ) + { + switch ( $propertyName ) + { + case 'assumedNumericCharacterWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['assumedNumericCharacterWidth'] = (float) $propertyValue; + break; + case 'assumedTextCharacterWidth': + if ( !is_numeric( $propertyValue ) || + ( $propertyValue < 0 ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'float > 0' ); + } + + $this->properties['assumedTextCharacterWidth'] = (float) $propertyValue; + break; + case 'strokeLineJoin': + $values = array( + 'round', + 'miter', + 'bevel', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['strokeLineJoin'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'strokeLineCap': + $values = array( + 'round', + 'butt', + 'square', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['strokeLineCap'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'shapeRendering': + $values = array( + 'auto', + 'optimizeSpeed', + 'crispEdges', + 'geometricPrecision', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['shapeRendering'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'colorRendering': + $values = array( + 'auto', + 'optimizeSpeed', + 'optimizeQuality', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['colorRendering'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'textRendering': + $values = array( + 'auto', + 'optimizeSpeed', + 'optimizeLegibility', + 'geometricPrecision', + 'inherit', + ); + + if ( in_array( $propertyValue, $values, true ) ) + { + $this->properties['textRendering'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, implode( $values, ', ' ) ); + } + break; + case 'templateDocument': + if ( !is_file( $propertyValue ) || !is_readable( $propertyValue ) ) + { + throw new ezcBaseFileNotFoundException( $propertyValue ); + } + else + { + $this->properties['templateDocument'] = realpath( $propertyValue ); + } + break; + case 'insertIntoGroup': + if ( !is_string( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'string' ); + } + else + { + $this->properties['insertIntoGroup'] = $propertyValue; + } + break; + case 'graphOffset': + if ( $propertyValue instanceof ezcGraphCoordinate ) + { + $this->properties['graphOffset'] = $propertyValue; + } + else + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'ezcGraphCoordinate' ); + } + break; + case 'idPrefix': + $this->properties['idPrefix'] = (string) $propertyValue; + break; + case 'encoding': + $this->properties['encoding'] = (string) $propertyValue; + break; + case 'linkCursor': + $this->properties['linkCursor'] = (string) $propertyValue; + break; + default: + parent::__set( $propertyName, $propertyValue ); + break; + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/palette/black.php b/include/ezcomponents/Graph/src/palette/black.php new file mode 100644 index 000000000..1e27fbac9 --- /dev/null +++ b/include/ezcomponents/Graph/src/palette/black.php @@ -0,0 +1,114 @@ + diff --git a/include/ezcomponents/Graph/src/palette/ez.php b/include/ezcomponents/Graph/src/palette/ez.php new file mode 100644 index 000000000..785d4c9e6 --- /dev/null +++ b/include/ezcomponents/Graph/src/palette/ez.php @@ -0,0 +1,97 @@ + diff --git a/include/ezcomponents/Graph/src/palette/ez_blue.php b/include/ezcomponents/Graph/src/palette/ez_blue.php new file mode 100644 index 000000000..d6eb8dc29 --- /dev/null +++ b/include/ezcomponents/Graph/src/palette/ez_blue.php @@ -0,0 +1,90 @@ + diff --git a/include/ezcomponents/Graph/src/palette/ez_green.php b/include/ezcomponents/Graph/src/palette/ez_green.php new file mode 100644 index 000000000..b7245c2b1 --- /dev/null +++ b/include/ezcomponents/Graph/src/palette/ez_green.php @@ -0,0 +1,90 @@ + diff --git a/include/ezcomponents/Graph/src/palette/ez_red.php b/include/ezcomponents/Graph/src/palette/ez_red.php new file mode 100644 index 000000000..841f6b6a8 --- /dev/null +++ b/include/ezcomponents/Graph/src/palette/ez_red.php @@ -0,0 +1,90 @@ + diff --git a/include/ezcomponents/Graph/src/palette/tango.php b/include/ezcomponents/Graph/src/palette/tango.php new file mode 100644 index 000000000..009432bdd --- /dev/null +++ b/include/ezcomponents/Graph/src/palette/tango.php @@ -0,0 +1,87 @@ + diff --git a/include/ezcomponents/Graph/src/renderer/2d.php b/include/ezcomponents/Graph/src/renderer/2d.php new file mode 100644 index 000000000..8021c039c --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/2d.php @@ -0,0 +1,1707 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->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' ); + * + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphRenderer2d + extends + ezcGraphRenderer + implements + ezcGraphRadarRenderer, ezcGraphStackedBarsRenderer, ezcGraphOdometerRenderer +{ + + /** + * Pie segment labels divided into two array, containing the labels on the + * left and right side of the pie chart center. + * + * @var array + */ + protected $pieSegmentLabels = array( + 0 => array(), + 1 => array(), + ); + + /** + * Contains the boundings used for pie segments + * + * @var ezcGraphBoundings + */ + protected $pieSegmentBoundings = false; + + /** + * Array with symbols for post processing, which ensures, that the symbols + * are rendered topmost. + * + * @var array + */ + protected $linePostSymbols = array(); + + /** + * Options + * + * @var ezcGraphRenderer2dOptions + */ + protected $options; + + /** + * Collect axis labels, so that the axis are drawn, when all axis spaces + * are known. + * + * @var array + */ + protected $axisLabels = array(); + + /** + * Collects circle sectors to draw shadow in background of all circle + * sectors. + * + * @var array + */ + protected $circleSectors = array(); + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->options = new ezcGraphRenderer2dOptions( $options ); + } + + /** + * __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 'options': + return $this->options; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Draw pie segment + * + * Draws a single pie segment + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of pie segment + * @param float $startAngle Start angle + * @param float $endAngle End angle + * @param mixed $label Label of pie segment + * @param bool $moveOut Move out from middle for hilighting + * @return void + */ + public function drawPieSegment( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + $startAngle = .0, + $endAngle = 360., + $label = false, + $moveOut = false ) + { + // Apply offset + $startAngle += $this->options->pieChartOffset; + $endAngle += $this->options->pieChartOffset; + + // Calculate position and size of pie + $center = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) / 2, + $boundings->y0 + ( $boundings->height ) / 2 + ); + + // Limit radius to fourth of width and half of height at maximum + $radius = min( + ( $boundings->width ) * $this->options->pieHorizontalSize, + ( $boundings->height ) * $this->options->pieVerticalSize + ); + + // Move pie segment out of the center + if ( $moveOut ) + { + $direction = ( $endAngle + $startAngle ) / 2; + + $center = new ezcGraphCoordinate( + $center->x + $this->options->moveOut * $radius * cos( deg2rad( $direction ) ), + $center->y + $this->options->moveOut * $radius * sin( deg2rad( $direction ) ) + ); + } + + // Add circle sector to queue + $this->circleSectors[] = array( + 'center' => $center, + 'context' => $context, + 'width' => $radius * 2 * ( 1 - $this->options->moveOut ), + 'height' => $radius * 2 * ( 1 - $this->options->moveOut ), + 'start' => $startAngle, + 'end' => $endAngle, + 'color' => $color, + ); + + if ( $label ) + { + // Determine position of label + $direction = ( $endAngle + $startAngle ) / 2; + $pieSegmentCenter = new ezcGraphCoordinate( + $center->x + cos( deg2rad( $direction ) ) * $radius, + $center->y + sin( deg2rad( $direction ) ) * $radius + ); + + // Split labels up into left an right size and index them on their + // y position + $this->pieSegmentLabels[(int) ($pieSegmentCenter->x > $center->x)][$pieSegmentCenter->y] = array( + new ezcGraphCoordinate( + $center->x + cos( deg2rad( $direction ) ) * $radius * 2 / 3, + $center->y + sin( deg2rad( $direction ) ) * $radius * 2 / 3 + ), + $label, + $context + ); + } + + if ( !$this->pieSegmentBoundings ) + { + $this->pieSegmentBoundings = $boundings; + } + } + + /** + * Draws the collected circle sectors + * + * All circle sectors are collected and drawn later to be able to render + * the shadows of the pie segments in the back of all pie segments. + * + * @return void + */ + protected function finishCircleSectors() + { + // Add circle sector sides to simple z buffer prioriry list + if ( $this->options->pieChartShadowSize > 0 ) + { + foreach ( $this->circleSectors as $circleSector ) + { + $this->driver->drawCircleSector( + new ezcGraphCoordinate( + $circleSector['center']->x + $this->options->pieChartShadowSize, + $circleSector['center']->y + $this->options->pieChartShadowSize + ), + $circleSector['width'], + $circleSector['height'], + $circleSector['start'], + $circleSector['end'], + $this->options->pieChartShadowColor->transparent( $this->options->pieChartShadowTransparency ), + true + ); + } + } + + foreach ( $this->circleSectors as $circleSector ) + { + // Draw circle sector + $this->addElementReference( + $circleSector['context'], + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'], + $circleSector['height'], + $circleSector['start'], + $circleSector['end'], + $circleSector['color'], + true + ) + ); + + $darkenedColor = $circleSector['color']->darken( $this->options->dataBorder ); + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'], + $circleSector['height'], + $circleSector['start'], + $circleSector['end'], + $darkenedColor, + false + ); + + if ( $this->options->pieChartGleam !== false ) + { + $gradient = new ezcGraphLinearGradient( + $circleSector['center'], + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y - $circleSector['height'] / 2 + ), + $this->options->pieChartGleamColor->transparent( 1 ), + $this->options->pieChartGleamColor->transparent( $this->options->pieChartGleam ) + ); + + $this->addElementReference( $circleSector['context'], + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'] - $this->options->pieChartGleamBorder * 2, + $circleSector['height'] - $this->options->pieChartGleamBorder * 2, + $circleSector['start'], + $circleSector['end'], + $gradient, + true + ) + ); + + $gradient = new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y + $circleSector['height'] / 4 + ), + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y + $circleSector['height'] / 2 + ), + $this->options->pieChartGleamColor->transparent( 1 ), + $this->options->pieChartGleamColor->transparent( $this->options->pieChartGleam ) + ); + + $this->addElementReference( $circleSector['context'], + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'] - $this->options->pieChartGleamBorder * 2, + $circleSector['height'] - $this->options->pieChartGleamBorder * 2, + $circleSector['start'], + $circleSector['end'], + $gradient, + true + ) + ); + } + } + } + + /** + * Draws the collected pie segment labels + * + * All labels are collected and drawn later to be able to partition the + * available space for the labels woth knowledge of the overall label + * count and their required size and optimal position. + * + * @return void + */ + protected function finishPieSegmentLabels() + { + if ( $this->pieSegmentBoundings === false ) + { + return true; + } + + $boundings = $this->pieSegmentBoundings; + + // Calculate position and size of pie + $center = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) / 2, + $boundings->y0 + ( $boundings->height ) / 2 + ); + + // Limit radius to fourth of width and half of height at maximum + $radius = min( + ( $boundings->width ) * $this->options->pieHorizontalSize, + ( $boundings->height ) * $this->options->pieVerticalSize + ); + + $pieChartHeight = min( + $radius * 2 + $radius / max( 1, count ( $this->pieSegmentLabels[0] ), count( $this->pieSegmentLabels[1] ) ) * 4, + $boundings->height + ); + $pieChartYPosition = $boundings->y0 + ( ( $boundings->height ) - $pieChartHeight ) / 2; + + // Calculate maximum height of labels + $labelHeight = min( + ( count( $this->pieSegmentLabels[0] ) + ? $pieChartHeight / count( $this->pieSegmentLabels[0] ) + : $pieChartHeight + ), + ( count( $this->pieSegmentLabels[1] ) + ? $pieChartHeight / count( $this->pieSegmentLabels[1] ) + : $pieChartHeight + ), + ( $pieChartHeight ) * $this->options->maxLabelHeight + ); + + $symbolSize = $this->options->symbolSize; + + foreach ( $this->pieSegmentLabels as $side => $labelPart ) + { + $minHeight = $pieChartYPosition; + $toShare = $pieChartHeight - count( $labelPart ) * $labelHeight; + + // Sort to draw topmost label first + ksort( $labelPart ); + $sign = ( $side ? -1 : 1 ); + + foreach ( $labelPart as $height => $label ) + { + if ( ( $height - $labelHeight / 2 ) > $minHeight ) + { + $share = min( $toShare, ( $height - $labelHeight / 2) - $minHeight ); + $minHeight += $share; + $toShare -= $share; + } + + // Determine position of label + $minHeight += max( 0, $height - $minHeight - $labelHeight ) / $pieChartHeight * $toShare; + $verticalDistance = ( $center->y - $minHeight - $labelHeight / 2 ) / $radius; + + $labelPosition = new ezcGraphCoordinate( + $center->x - + $sign * ( + abs( $verticalDistance ) > 1 + // If vertical distance to center is greater then the + // radius, use the centerline for the horizontal + // position + ? max ( + 5, + abs( $label[0]->x - $center->x ) + ) + // Else place the label outside of the pie chart + : ( cos ( asin ( $verticalDistance ) ) * $radius + + $symbolSize * (int) $this->options->showSymbol + ) + ), + $minHeight + $labelHeight / 2 + ); + + if ( $this->options->showSymbol ) + { + // Draw label + $this->driver->drawLine( + $label[0], + $labelPosition, + $this->options->pieChartSymbolColor, + 1 + ); + + $this->driver->drawCircle( + $label[0], + $symbolSize, + $symbolSize, + $this->options->pieChartSymbolColor, + true + ); + $this->driver->drawCircle( + $labelPosition, + $symbolSize, + $symbolSize, + $this->options->pieChartSymbolColor, + true + ); + } + + $this->addElementReference( + $label[2], + $this->driver->drawTextBox( + $label[1], + new ezcGraphCoordinate( + ( !$side ? $boundings->x0 : $labelPosition->x + $symbolSize ), + $minHeight + ), + ( !$side ? $labelPosition->x - $boundings->x0 - $symbolSize : $boundings->x1 - $labelPosition->x - $symbolSize ), + $labelHeight, + ( !$side ? ezcGraph::RIGHT : ezcGraph::LEFT ) | ezcGraph::MIDDLE + ) + ); + + // Add used space to minHeight + $minHeight += $labelHeight; + } + } + } + + /** + * Draw the collected line symbols + * + * Symbols for the data lines are collected and delayed to ensure that + * they are not covered and hidden by other data lines. + * + * @return void + */ + protected function finishLineSymbols() + { + foreach ( $this->linePostSymbols as $symbol ) + { + $this->addElementReference( + $symbol['context'], + $this->drawSymbol( + $symbol['boundings'], + $symbol['color'], + $symbol['symbol'] + ) + ); + } + } + + /** + * Draw bar + * + * Draws a bar as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $position Position of data point + * @param float $stepSize Space which can be used for bars + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param int $symbol Symbol to draw for line + * @param float $axisPosition Position of axis for drawing filled lines + * @return void + */ + public function drawBar( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $position, + $stepSize, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + $axisPosition = 0. ) + { + // Apply margin + $margin = $stepSize * $this->options->barMargin; + $padding = $stepSize * $this->options->barPadding; + $barWidth = ( $stepSize - $margin ) / $dataCount - $padding; + $offset = - $stepSize / 2 + $margin / 2 + ( $dataCount - $dataNumber -1 ) * ( $padding + $barWidth ) + $padding / 2; + + $barPointArray = array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + ); + + $this->addElementReference( + $context, + $this->driver->drawPolygon( + $barPointArray, + $color, + true + ) + ); + + if ( $this->options->dataBorder > 0 ) + { + $darkened = $color->darken( $this->options->dataBorder ); + $this->driver->drawPolygon( + $barPointArray, + $darkened, + false, + 1 + ); + } + } + + /** + * Draw stacked bar + * + * Draws a stacked bar part as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $start + * @param ezcGraphCoordinate $position + * @param float $stepSize Space which can be used for bars + * @param int $symbol Symbol to draw for line + * @param float $axisPosition Position of axis for drawing filled lines + * @return void + */ + public function drawStackedBar( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $start, + ezcGraphCoordinate $position, + $stepSize, + $symbol = ezcGraph::NO_SYMBOL, + $axisPosition = 0. ) + { + // Apply margin + $margin = $stepSize * $this->options->barMargin; + $barWidth = $stepSize - $margin; + $offset = - $stepSize / 2 + $margin / 2; + + $barPointArray = array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $position->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $position->x + $offset + $barWidth, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + ); + + $this->addElementReference( + $context, + $this->driver->drawPolygon( + $barPointArray, + $color, + true + ) + ); + + if ( $this->options->dataBorder > 0 ) + { + $darkened = $color->darken( $this->options->dataBorder ); + $this->driver->drawPolygon( + $barPointArray, + $darkened, + false, + 1 + ); + } + } + + /** + * Draw data line + * + * Draws a line as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $start Starting point + * @param ezcGraphCoordinate $end Ending point + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param int $symbol Symbol to draw for line + * @param ezcGraphColor $symbolColor Color of the symbol, defaults to linecolor + * @param ezcGraphColor $fillColor Color to fill line with + * @param float $axisPosition Position of axis for drawing filled lines + * @param float $thickness Line thickness + * @return void + */ + public function drawDataLine( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + ezcGraphColor $symbolColor = null, + ezcGraphColor $fillColor = null, + $axisPosition = 0., + $thickness = 1. ) + { + // Perhaps fill up line + if ( $fillColor !== null && + $start->x != $end->x ) + { + $startValue = $axisPosition - $start->y; + $endValue = $axisPosition - $end->y; + + if ( ( $startValue == 0 ) || + ( $endValue == 0 ) || + ( $startValue / abs( $startValue ) == $endValue / abs( $endValue ) ) ) + { + // Values have the same sign or are on the axis + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $end->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + ), + $fillColor, + true + ); + } + else + { + // values are on differente sides of the axis - split the filled polygon + $startDiff = abs( $axisPosition - $start->y ); + $endDiff = abs( $axisPosition - $end->y ); + + $cuttingPosition = $startDiff / ( $endDiff + $startDiff ); + $cuttingPoint = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $cuttingPosition, + $axisPosition + ); + + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $cuttingPoint->x, + $boundings->y0 + ( $boundings->height ) * $cuttingPoint->y + ), + ), + $fillColor, + true + ); + + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $axisPosition + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $end->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $cuttingPoint->x, + $boundings->y0 + ( $boundings->height ) * $cuttingPoint->y + ), + ), + $fillColor, + true + ); + } + } + + // Draw line + $this->driver->drawLine( + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $start->x, + $boundings->y0 + ( $boundings->height ) * $start->y + ), + new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $end->y + ), + $color, + $thickness + ); + + // Draw line symbol + if ( $symbol !== ezcGraph::NO_SYMBOL ) + { + if ( $symbolColor === null ) + { + $symbolColor = $color; + } + + $this->linePostSymbols[] = array( + 'boundings' => new ezcGraphBoundings( + $boundings->x0 + ( $boundings->width ) * $end->x - $this->options->symbolSize / 2, + $boundings->y0 + ( $boundings->height ) * $end->y - $this->options->symbolSize / 2, + $boundings->x0 + ( $boundings->width ) * $end->x + $this->options->symbolSize / 2, + $boundings->y0 + ( $boundings->height ) * $end->y + $this->options->symbolSize / 2 + ), + 'color' => $symbolColor, + 'context' => $context, + 'symbol' => $symbol, + ); + } + } + + /** + * Returns a coordinate in the given bounding box for the given angle + * radius with the center as base point. + * + * @param ezcGraphBoundings $boundings + * @param ezcGraphCoordinate $center + * @param float $angle + * @param float $radius + * @return float + */ + protected function getCoordinateFromAngleAndRadius( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $center, + $angle, + $radius + ) + { + $direction = new ezcGraphCoordinate( + sin( $angle ) * $boundings->width / 2, + -cos( $angle ) * $boundings->height / 2 + ); + + $offset = new ezcGraphCoordinate( + sin( $angle ) * $this->xAxisSpace, + -cos( $angle ) * $this->yAxisSpace + ); + + $direction->x -= 2 * $offset->x; + $direction->y -= 2 * $offset->y; + + return new ezcGraphCoordinate( + $boundings->x0 + + $center->x + + $offset->x + + $direction->x * $radius, + $boundings->y0 + + $center->y + + $offset->y + + $direction->y * $radius + ); + } + + /** + * Draw radar chart data line + * + * Draws a line as a data element in a radar chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $center Center of radar chart + * @param ezcGraphCoordinate $start Starting point + * @param ezcGraphCoordinate $end Ending point + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param int $symbol Symbol to draw for line + * @param ezcGraphColor $symbolColor Color of the symbol, defaults to linecolor + * @param ezcGraphColor $fillColor Color to fill line with + * @param float $thickness Line thickness + * @return void + */ + public function drawRadarDataLine( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $center, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + ezcGraphColor $symbolColor = null, + ezcGraphColor $fillColor = null, + $thickness = 1. + ) + { + // Calculate line points from chart coordinates + $start = $this->getCoordinateFromAngleAndRadius( + $boundings, + $center, + $start->x * 2 * M_PI, + $start->y + ); + $end = $this->getCoordinateFromAngleAndRadius( + $boundings, + $center, + $end->x * 2 * M_PI, + $end->y + ); + + // Fill line + if ( $fillColor !== null ) + { + $this->driver->drawPolygon( + array( + $start, + $end, + new ezcGraphCoordinate( + $boundings->x0 + $center->x, + $boundings->y0 + $center->y + ), + ), + $fillColor, + true + ); + } + + // Draw line + $this->driver->drawLine( + $start, + $end, + $color, + $thickness + ); + + if ( $symbol !== ezcGraph::NO_SYMBOL ) + { + $this->drawSymbol( + new ezcGraphBoundings( + $end->x - $this->options->symbolSize / 2, + $end->y - $this->options->symbolSize / 2, + $end->x + $this->options->symbolSize / 2, + $end->y + $this->options->symbolSize / 2 + ), + $symbolColor, + $symbol + ); + } + } + + /** + * Draws a highlight textbox for a datapoint. + * + * A highlight textbox for line and bar charts means a box with the current + * value in the graph. + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphCoordinate $end Ending point + * @param float $axisPosition Position of axis for drawing filled lines + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param ezcGraphFontOptions $font Font used for highlight string + * @param string $text Acutual value + * @param int $size Size of highlight text + * @param ezcGraphColor $markLines + * @return void + */ + public function drawDataHighlightText( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphCoordinate $end, + $axisPosition = 0., + $dataNumber = 1, + $dataCount = 1, + ezcGraphFontOptions $font, + $text, + $size, + ezcGraphColor $markLines = null ) + { + $this->driver->options->font = $font; + $width = $boundings->width / $dataCount; + + $dataPoint = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->width ) * $end->x, + $boundings->y0 + ( $boundings->height ) * $end->y + ); + + if ( $end->y < $axisPosition ) + { + $this->driver->drawTextBox( + $text, + new ezcGraphCoordinate( + $dataPoint->x - $width / 2, + $dataPoint->y - $size - $font->padding - $this->options->symbolSize + ), + $width, + $size, + ezcGraph::CENTER | ezcGraph::BOTTOM + ); + } + else + { + $this->driver->drawTextBox( + $text, + new ezcGraphCoordinate( + $dataPoint->x - $width / 2, + $dataPoint->y + $font->padding + $this->options->symbolSize + ), + $width, + $size, + ezcGraph::CENTER | ezcGraph::TOP + ); + } + } + + /** + * Draw legend + * + * Will draw a legend in the bounding box + * + * @param ezcGraphBoundings $boundings Bounding of legend + * @param ezcGraphChartElementLegend $legend Legend to draw; + * @param int $type Type of legend: Protrait or landscape + * @return void + */ + public function drawLegend( + ezcGraphBoundings $boundings, + ezcGraphChartElementLegend $legend, + $type = ezcGraph::VERTICAL ) + { + $labels = $legend->labels; + + // Calculate boundings of each label + if ( $type & ezcGraph::VERTICAL ) + { + $labelWidth = $boundings->width; + $labelHeight = min( + ( $boundings->height ) / count( $labels ) - $legend->spacing, + $legend->symbolSize + 2 * $legend->padding + ); + } + else + { + $labelWidth = ( $boundings->width ) / count( $labels ) - $legend->spacing; + $labelHeight = min( + $boundings->height, + $legend->symbolSize + 2 * $legend->padding + ); + } + + $symbolSize = $labelHeight - 2 * $legend->padding; + + // Draw all labels + $labelPosition = new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ); + foreach ( $labels as $label ) + { + $this->elements['legend_url'][$label['label']] = $label['url']; + + $this->elements['legend'][$label['label']]['symbol'] = $this->drawSymbol( + new ezcGraphBoundings( + $labelPosition->x + $legend->padding, + $labelPosition->y + $legend->padding, + $labelPosition->x + $legend->padding + $symbolSize, + $labelPosition->y + $legend->padding + $symbolSize + ), + $label['color'], + $label['symbol'] + ); + + $this->elements['legend'][$label['label']]['text'] = $this->driver->drawTextBox( + $label['label'], + new ezcGraphCoordinate( + $labelPosition->x + 2 * $legend->padding + $symbolSize, + $labelPosition->y + $legend->padding + ), + $labelWidth - $symbolSize - 3 * $legend->padding, + $labelHeight - 2 * $legend->padding, + ezcGraph::LEFT | ezcGraph::MIDDLE + ); + + $labelPosition->x += ( $type === ezcGraph::VERTICAL ? 0 : $labelWidth + $legend->spacing ); + $labelPosition->y += ( $type === ezcGraph::VERTICAL ? $labelHeight + $legend->spacing : 0 ); + } + } + + /** + * Draw box + * + * Box are wrapping each major chart element and draw border, background + * and title to each chart element. + * + * Optionally a padding and margin for each box can be defined. + * + * @param ezcGraphBoundings $boundings Boundings of the box + * @param ezcGraphColor $background Background color + * @param ezcGraphColor $borderColor Border color + * @param int $borderWidth Border width + * @param int $margin Margin + * @param int $padding Padding + * @param mixed $title Title of the box + * @param int $titleSize Size of title in the box + * @return ezcGraphBoundings Remaining inner boundings + */ + public function drawBox( + ezcGraphBoundings $boundings, + ezcGraphColor $background = null, + ezcGraphColor $borderColor = null, + $borderWidth = 0, + $margin = 0, + $padding = 0, + $title = false, + $titleSize = 16 ) + { + // Apply margin + $boundings->x0 += $margin; + $boundings->y0 += $margin; + $boundings->x1 -= $margin; + $boundings->y1 -= $margin; + + if ( $background instanceof ezcGraphColor ) + { + // Draw box background + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + new ezcGraphCoordinate( $boundings->x0, $boundings->y1 ), + ), + $background, + true + ); + } + + if ( ( $borderColor instanceof ezcGraphColor ) && + ( $borderWidth > 0 ) ) + { + // Draw border + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + new ezcGraphCoordinate( $boundings->x0, $boundings->y1 ), + ), + $borderColor, + false, + $borderWidth + ); + + // Reduce local boundings by borderWidth + $boundings->x0 += $borderWidth; + $boundings->y0 += $borderWidth; + $boundings->x1 -= $borderWidth; + $boundings->y1 -= $borderWidth; + } + + // Apply padding + $boundings->x0 += $padding; + $boundings->y0 += $padding; + $boundings->x1 -= $padding; + $boundings->y1 -= $padding; + + // Add box title + if ( $title !== false ) + { + switch ( $this->options->titlePosition ) + { + case ezcGraph::TOP: + $this->driver->drawTextBox( + $title, + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + $boundings->width, + $titleSize, + $this->options->titleAlignement + ); + + $boundings->y0 += $titleSize + $padding; + $boundings->y1 -= $titleSize + $padding; + break; + case ezcGraph::BOTTOM: + $this->driver->drawTextBox( + $title, + new ezcGraphCoordinate( $boundings->x0, $boundings->y1 - $titleSize ), + $boundings->width, + $titleSize, + $this->options->titleAlignement + ); + + $boundings->y1 -= $titleSize + $padding; + break; + } + } + + return $boundings; + } + + /** + * Draw text + * + * Draws the provided text in the boundings + * + * @param ezcGraphBoundings $boundings Boundings of text + * @param string $text Text + * @param int $align Alignement of text + * @param ezcGraphRotation $rotation + * @return void + */ + public function drawText( + ezcGraphBoundings $boundings, + $text, + $align = ezcGraph::LEFT, + ezcGraphRotation $rotation = null ) + { + $this->driver->drawTextBox( + $text, + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + $boundings->width, + $boundings->height, + $align, + $rotation + ); + } + + /** + * Draw grid line + * + * Draw line for the grid in the chart background + * + * @param ezcGraphCoordinate $start Start point + * @param ezcGraphCoordinate $end End point + * @param ezcGraphColor $color Color of the grid line + * @return void + */ + public function drawGridLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color ) + { + $this->driver->drawLine( + $start, + $end, + $color, + 1 + ); + } + + /** + * Draw step line + * + * Draw a step (marker for label position) on a axis. + * + * @param ezcGraphCoordinate $start Start point + * @param ezcGraphCoordinate $end End point + * @param ezcGraphColor $color Color of the grid line + * @return void + */ + public function drawStepLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color ) + { + $this->driver->drawLine( + $start, + $end, + $color, + 1 + ); + } + + /** + * Draw axis + * + * Draws an axis form the provided start point to the end point. A specific + * angle of the axis is not required. + * + * For the labeleing of the axis a sorted array with major steps and an + * array with minor steps is expected, which are build like this: + * array( + * array( + * 'position' => (float), + * 'label' => (string), + * ) + * ) + * where the label is optional. + * + * The label renderer class defines how the labels are rendered. For more + * documentation on this topic have a look at the basic label renderer + * class. + * + * Additionally it can be specified if a major and minor grid are rendered + * by defining a color for them. Teh axis label is used to add a caption + * for the axis. + * + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $start Start point of axis + * @param ezcGraphCoordinate $end Endpoint of axis + * @param ezcGraphChartElementAxis $axis Axis to render + * @param ezcGraphAxisLabelRenderer $labelClass Used label renderer + * @return void + */ + public function drawAxis( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphAxisLabelRenderer $labelClass = null ) + { + // Store axis space for use by label renderer + switch ( $axis->position ) + { + case ezcGraph::TOP: + case ezcGraph::BOTTOM: + $this->xAxisSpace = ( $boundings->width ) * $axis->axisSpace; + break; + case ezcGraph::LEFT: + case ezcGraph::RIGHT: + $this->yAxisSpace = ( $boundings->height ) * $axis->axisSpace; + break; + } + + // Clone boundings because of internal modifications + $boundings = clone $boundings; + + $start->x += $boundings->x0; + $start->y += $boundings->y0; + $end->x += $boundings->x0; + $end->y += $boundings->y0; + + // Determine normalized direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + // Draw axis + $this->driver->drawLine( + $start, + $end, + $axis->border, + 1 + ); + + // Draw small arrowhead + $size = max( + $axis->minArrowHeadSize, + min( + $axis->maxArrowHeadSize, + abs( ceil( ( ( $end->x - $start->x ) + ( $end->y - $start->y ) ) * $axis->axisSpace / 4 ) ) + ) + ); + + $orthogonalDirection = clone $direction; + $orthogonalDirection->rotateClockwise(); + + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( + $end->x, + $end->y + ), + new ezcGraphCoordinate( + $end->x + - $orthogonalDirection->x * $size / 2 + + $direction->x * $size, + $end->y + - $orthogonalDirection->y * $size / 2 + + $direction->y * $size + ), + new ezcGraphCoordinate( + $end->x + + $orthogonalDirection->x * $size / 2 + + $direction->x * $size, + $end->y + + $orthogonalDirection->y * $size / 2 + + $direction->y * $size + ), + ), + $axis->border, + true + ); + + // Draw axis label + if ( $axis->label !== false ) + { + $width = $boundings->width; + switch ( $axis->position ) + { + case ezcGraph::TOP: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $end->x + $axis->labelMargin - $width * ( 1 - $axis->axisSpace * 2 ), + $end->y - $axis->labelMargin - $axis->labelSize + ), + $width * ( 1 - $axis->axisSpace * 2 ) - $axis->labelMargin, + $axis->labelSize, + ezcGraph::TOP | ezcGraph::RIGHT + ); + break; + case ezcGraph::BOTTOM: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $end->x + $axis->labelMargin, + $end->y + $axis->labelMargin + ), + $width * ( 1 - $axis->axisSpace * 2 ) - $axis->labelMargin, + $axis->labelSize, + ezcGraph::TOP | ezcGraph::LEFT + ); + break; + case ezcGraph::LEFT: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $end->x - $width, + $end->y - $axis->labelSize - $axis->labelMargin + ), + $width - $axis->labelMargin, + $axis->labelSize, + ezcGraph::BOTTOM | ezcGraph::RIGHT + ); + break; + case ezcGraph::RIGHT: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $end->x, + $end->y - $axis->labelSize - $axis->labelMargin + ), + $width - $axis->labelMargin, + $axis->labelSize, + ezcGraph::BOTTOM | ezcGraph::LEFT + ); + break; + } + } + + // Collect axis labels and draw, when all axisSpaces are collected + $this->axisLabels[] = array( + 'object' => $labelClass, + 'boundings' => $boundings, + 'start' => clone $start, + 'end' => clone $end, + 'axis' => $axis, + ); + + if ( $this->xAxisSpace && $this->yAxisSpace ) + { + $this->drawAxisLabels(); + } + } + + /** + * Draw all left axis labels + * + * @return void + */ + protected function drawAxisLabels() + { + foreach ( $this->axisLabels as $nr => $axisLabel ) + { + // If font should not be synchronized, use font configuration from + // each axis + if ( $this->options->syncAxisFonts === false ) + { + $this->driver->options->font = $axisLabel['axis']->font; + } + + $start = $axisLabel['start']; + $end = $axisLabel['end']; + + $direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $direction->unify(); + + // Convert elipse to circle for correct angle calculation + $direction->y *= ( $this->xAxisSpace / $this->yAxisSpace ); + $angle = $direction->angle( new ezcGraphVector( 0, 1 ) ); + + $movement = new ezcGraphVector( + sin( $angle ) * $this->xAxisSpace * ( $direction->x < 0 ? -1 : 1 ), + cos( $angle ) * $this->yAxisSpace + ); + + $start->x += $movement->x; + $start->y += $movement->y; + $end->x -= $movement->x; + $end->y -= $movement->y; + + $axisLabel['object']->renderLabels( + $this, + $axisLabel['boundings'], + $start, + $end, + $axisLabel['axis'] + ); + + // Prevent from redrawing axis on more then 2 axis. + unset( $this->axisLabels[$nr] ); + } + } + + /** + * Draw background image + * + * Draws a background image at the defined position. If repeat is set the + * background image will be repeated like any texture. + * + * @param ezcGraphBoundings $boundings Boundings for the background image + * @param string $file Filename of background image + * @param int $position Position of background image + * @param int $repeat Type of repetition + * @return void + */ + public function drawBackgroundImage( + ezcGraphBoundings $boundings, + $file, + $position = 48, // ezcGraph::CENTER | ezcGraph::MIDDLE + $repeat = ezcGraph::NO_REPEAT ) + { + $imageData = getimagesize( $file ); + $imageWidth = $imageData[0]; + $imageHeight = $imageData[1]; + + $imageWidth = min( $imageWidth, $boundings->width ); + $imageHeight = min( $imageHeight, $boundings->height ); + + $imagePosition = new ezcGraphCoordinate( + $boundings->x0, + $boundings->y0 + ); + + // Determine x position + switch ( true ) { + case ( $repeat & ezcGraph::HORIZONTAL ): + // If is repeated on this axis fall back to position zero + case ( $position & ezcGraph::LEFT ): + $imagePosition->x = $boundings->x0; + break; + case ( $position & ezcGraph::RIGHT ): + $imagePosition->x = max( + $boundings->x1 - $imageWidth, + $boundings->x0 + ); + break; + default: + $imagePosition->x = max( + $boundings->x0 + ( $boundings->width - $imageWidth ) / 2, + $boundings->x0 + ); + break; + } + + // Determine y position + switch ( true ) { + case ( $repeat & ezcGraph::VERTICAL ): + // If is repeated on this axis fall back to position zero + case ( $position & ezcGraph::TOP ): + $imagePosition->y = $boundings->y0; + break; + case ( $position & ezcGraph::BOTTOM ): + $imagePosition->y = max( + $boundings->y1 - $imageHeight, + $boundings->y0 + ); + break; + default: + $imagePosition->y = max( + $boundings->y0 + ( $boundings->height - $imageHeight ) / 2, + $boundings->y0 + ); + break; + } + + // Texturize backround based on position and repetition + $position = new ezcGraphCoordinate( + $imagePosition->x, + $imagePosition->y + ); + + do + { + $position->y = $imagePosition->y; + + do + { + $this->driver->drawImage( + $file, + $position, + $imageWidth, + $imageHeight + ); + + $position->y += $imageHeight; + } + while ( ( $position->y < $boundings->y1 ) && + ( $repeat & ezcGraph::VERTICAL ) ); + + $position->x += $imageWidth; + } + while ( ( $position->x < $boundings->x1 ) && + ( $repeat & ezcGraph::HORIZONTAL ) ); + } + + /** + * Call all postprocessing functions + * + * @return void + */ + protected function finish() + { + $this->finishCircleSectors(); + $this->finishPieSegmentLabels(); + $this->finishLineSymbols(); + + return true; + } + + /** + * Reset renderer properties + * + * Reset all renderer properties, which were calculated during the + * rendering process, to offer a clean environment for rerendering. + * + * @return void + */ + protected function resetRenderer() + { + parent::resetRenderer(); + + // Also reset special 2D renderer options + $this->pieSegmentLabels = array( + 0 => array(), + 1 => array(), + ); + $this->pieSegmentBoundings = false; + $this->linePostSymbols = array(); + $this->axisLabels = array(); + $this->circleSectors = array(); + } + + /** + * Render odometer chart + * + * @param ezcGraphBoundings $boundings + * @param ezcGraphChartElementAxis $axis + * @param ezcGraphOdometerChartOptions $options + * @return ezcGraphBoundings + */ + public function drawOdometer( + ezcGraphBoundings $boundings, + ezcGraphChartElementAxis $axis, + ezcGraphOdometerChartOptions $options ) + { + $height = $boundings->height * $options->odometerHeight; + + // Draw axis + $oldAxisSpace = $axis->axisSpace; + $axis->axisSpace = 0; + + $axis->render( $this, $boundings ); + + // Reset axisspaces to correct values + $this->xAxisSpace = $boundings->width * $oldAxisSpace; + $this->yAxisSpace = ( $boundings->height - $height ) / 2; + + $this->drawAxisLabels(); + + // Reduce size of chart boundings respecting requested odometer height + $boundings->x0 += $this->xAxisSpace; + $boundings->x1 -= $this->xAxisSpace; + $boundings->y0 += $this->yAxisSpace; + $boundings->y1 -= $this->yAxisSpace; + + $gradient = new ezcGraphLinearGradient( + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ), + $options->startColor, + $options->endColor + ); + + // Simply draw box with gradient and optional border + $this->drawBox( + $boundings, + $gradient, + $options->borderColor, + $options->borderWidth + ); + + // Return modified chart boundings + return $boundings; + } + + /** + * Draw a single odometer marker. + * + * @param ezcGraphBoundings $boundings + * @param ezcGraphCoordinate $position + * @param int $symbol + * @param ezcGraphColor $color + * @param int $width + */ + public function drawOdometerMarker( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $position, + $symbol, + ezcGraphColor $color, + $width ) + { + $this->driver->drawLine( + new ezcGraphCoordinate( + $xPos = $boundings->x0 + ( $position->x * $boundings->width ), + $boundings->y0 + ), + new ezcGraphCoordinate( + $xPos, + $boundings->y1 + ), + $color, + $width + ); + } +} + +?> diff --git a/include/ezcomponents/Graph/src/renderer/3d.php b/include/ezcomponents/Graph/src/renderer/3d.php new file mode 100644 index 000000000..4c6e1436f --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/3d.php @@ -0,0 +1,2407 @@ + + * $graph = new ezcGraphPieChart(); + * $graph->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' ); + * + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphRenderer3d + extends + ezcGraphRenderer + implements + ezcGraphStackedBarsRenderer +{ + + /** + * Pie segment labels divided into two array, containing the labels on the + * left and right side of the pie chart center. + * + * @var array + */ + protected $pieSegmentLabels = array( + 0 => array(), + 1 => array(), + ); + + /** + * Contains the boundings used for pie segments + * + * @var ezcGraphBoundings + */ + protected $pieSegmentBoundings = false; + + /** + * Array with symbols for post processing, which ensures, that the symbols + * are rendered topmost. + * + * @var array + */ + protected $linePostSymbols = array(); + + /** + * Array containing lines from the axis and grid which should be redrawn on + * top of the data. + * + * @var array + */ + protected $frontLines = array(); + + /** + * Collects circle sectors to draw shadow in background of all circle + * sectors. + * + * @var array + */ + protected $circleSectors = array(); + + /** + * Collects bar sides to draw them in a post processing step to simulate + * a simple z buffer. + * array( + * array( + * 'index' => (int) // used for sorting + * 'context' => ezcGraphContext // context of call + * 'method' => (string) // method of driver to call + * 'parameters' => array // parameters for method call + * ), ... + * ) + * + * @var array + */ + protected $barPostProcessing = array(); + + /** + * Options + * + * @var ezcGraphRenderer3dOptions + */ + protected $options; + + /** + * Depth of displayed pseudo three dimensional line chart elements. + * + * @var float + */ + protected $depth = false; + + /** + * Factor to reduce the width according to depth + * + * @var float + */ + protected $xDepthFactor = false; + + /** + * Factor to reduce the height according to depth + * + * @var float + */ + protected $yDepthFactor = false; + + /** + * Boundings for the chart data + * + * @var ezcGraphBoundings + */ + protected $dataBoundings = false; + + /** + * Collect axis labels, so that the axis are drawn, when all axis spaces + * are known. + * + * @var array + */ + protected $axisLabels = array(); + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->options = new ezcGraphRenderer3dOptions( $options ); + } + + /** + * __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 'options': + return $this->options; + default: + return parent::__get( $propertyName ); + } + } + + /** + * Calculate the display coordinate from a coordinate + * + * Calculates the display coordinate of a coordinate depending on the + * depth setting and the distance of the coordinate to the front of the + * chart. + * + * @param ezcGraphCoordinate $c Coordinate + * @param float $front Distance to front (0 - 1) + * @return ezcGraphCoordinate Resulting coordinate + */ + protected function get3dCoordinate( ezcGraphCoordinate $c, $front = 1. ) + { + return new ezcGraphCoordinate( + ( $c->x - $this->dataBoundings->x0 ) * $this->xDepthFactor + $this->dataBoundings->x0 + $this->depth * $front, + ( $c->y - $this->dataBoundings->y0 ) * $this->yDepthFactor + $this->dataBoundings->y0 + $this->depth * ( 1 - $front ) + ); + } + + /** + * Draw pie segment + * + * Draws a single pie segment + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of pie segment + * @param float $startAngle Start angle + * @param float $endAngle End angle + * @param mixed $label Label of pie segment + * @param bool $moveOut Move out from middle for hilighting + * @return void + */ + public function drawPieSegment( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + $startAngle = .0, + $endAngle = 360., + $label = false, + $moveOut = false ) + { + // Apply offset + $startAngle += $this->options->pieChartOffset; + $endAngle += $this->options->pieChartOffset; + + // Calculate position and size of pie + $center = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + - $this->options->pieChartHeight / 2 + ); + + // Limit radius to fourth of width and half of height at maximum + $radius = min( + ( $boundings->x1 - $boundings->x0 ) * $this->options->pieHorizontalSize, + ( $boundings->y1 - $boundings->y0 ) * $this->options->pieVerticalSize + ); + + // Move pie segment out of the center + if ( $moveOut ) + { + $direction = ( $endAngle + $startAngle ) / 2; + + $center = new ezcGraphCoordinate( + $center->x + $this->options->moveOut * $radius * cos( deg2rad( $direction ) ), + $center->y + $this->options->moveOut * $radius * sin( deg2rad( $direction ) ) * $this->options->pieChartRotation + ); + } + + // Add circle sector to queue + $this->circleSectors[] = array( + 'center' => $center, + 'context' => $context, + 'width' => $radius * 2 * ( 1 - $this->options->moveOut ), + 'height' => $radius * 2 * ( 1 - $this->options->moveOut ) * $this->options->pieChartRotation - $this->options->pieChartHeight, + 'start' => $startAngle, + 'end' => $endAngle, + 'color' => $color, + ); + + if ( $label ) + { + // Determine position of label + $direction = ( $endAngle + $startAngle ) / 2; + $pieSegmentCenter = new ezcGraphCoordinate( + $center->x + cos( deg2rad( $direction ) ) * $radius, + $center->y + sin( deg2rad( $direction ) ) * $radius * $this->options->pieChartRotation + ); + + // Split labels up into left a right site and index them on their + // y position + $this->pieSegmentLabels[(int) ($pieSegmentCenter->x > $center->x)][(int) ( $pieSegmentCenter->y * 100 )] = array( + new ezcGraphCoordinate( + $center->x + cos( deg2rad( $direction ) ) * $radius * 2 / 3 * ( 1 - $this->options->moveOut ), + $center->y + sin( deg2rad( $direction ) ) * ( $radius - $this->options->pieChartHeight ) * 2 / 3 * ( 1 - $this->options->moveOut ) * $this->options->pieChartRotation + ), + $label, + $context, + ); + } + + if ( !$this->pieSegmentBoundings ) + { + $this->pieSegmentBoundings = $boundings; + } + } + + /** + * Draws the collected pie segment labels + * + * All labels are collected and drawn later to be able to partition the + * available space for the labels woth knowledge of the overall label + * count and their required size and optimal position. + * + * @return void + */ + protected function finishPieSegmentLabels() + { + if ( $this->pieSegmentBoundings === false ) + { + return true; + } + + $boundings = $this->pieSegmentBoundings; + + // Calculate position and size of pie + $center = new ezcGraphCoordinate( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 ) / 2, + $boundings->y0 + ( $boundings->y1 - $boundings->y0 ) / 2 + ); + + // Limit radius to fourth of width and half of height at maximum + $radius = min( + ( $boundings->width ) * $this->options->pieHorizontalSize, + ( $boundings->height ) * $this->options->pieVerticalSize + ); + + $pieChartHeight = min( + $radius * 2 + $radius / max( 1, count ( $this->pieSegmentLabels[0] ), count( $this->pieSegmentLabels[1] ) ) * 4, + $boundings->height + ); + $pieChartYPosition = $boundings->y0 + ( ( $boundings->height ) - $pieChartHeight ) / 2; + + // Calculate maximum height of labels + $labelHeight = min( + ( count( $this->pieSegmentLabels[0] ) + ? $pieChartHeight / count( $this->pieSegmentLabels[0] ) + : $pieChartHeight + ), + ( count( $this->pieSegmentLabels[1] ) + ? $pieChartHeight / count( $this->pieSegmentLabels[1] ) + : $pieChartHeight + ), + ( $pieChartHeight ) * $this->options->maxLabelHeight + ); + + $symbolSize = $this->options->symbolSize; + + foreach ( $this->pieSegmentLabels as $side => $labelPart ) + { + $minHeight = $pieChartYPosition; + $toShare = $pieChartHeight - count( $labelPart ) * $labelHeight; + + // Sort to draw topmost label first + ksort( $labelPart ); + $sign = ( $side ? -1 : 1 ); + + foreach ( $labelPart as $height => $label ) + { + $height = (int) ( $height / 100 ); + + if ( ( $height - $labelHeight / 2 ) > $minHeight ) + { + $share = min( $toShare, ( $height - $labelHeight / 2) - $minHeight ); + $minHeight += $share; + $toShare -= $share; + } + + // Determine position of label + $minHeight += max( 0, $height - $minHeight - $labelHeight ) / $pieChartHeight * $toShare; + $verticalDistance = ( $center->y - $minHeight - $labelHeight / 2 ) / $radius; + + $labelPosition = new ezcGraphCoordinate( + $center->x - + $sign * ( + abs( $verticalDistance ) > 1 + // If vertical distance to center is greater then the + // radius, use the centerline for the horizontal + // position + ? max ( + 5, + abs( $label[0]->x - $center->x ) + ) + // Else place the label outside of the pie chart + : ( cos ( asin ( $verticalDistance ) ) * $radius + + $symbolSize * (int) $this->options->showSymbol + ) + ), + $minHeight + $labelHeight / 2 + ); + + if ( $this->options->showSymbol ) + { + // Draw label + $this->driver->drawLine( + $label[0], + $labelPosition, + $this->options->pieChartSymbolColor, + 1 + ); + + $this->driver->drawCircle( + $label[0], + $symbolSize, + $symbolSize, + $this->options->pieChartSymbolColor, + true + ); + $this->driver->drawCircle( + $labelPosition, + $symbolSize, + $symbolSize, + $this->options->pieChartSymbolColor, + true + ); + } + + $this->addElementReference( $label[2], + $this->driver->drawTextBox( + $label[1], + new ezcGraphCoordinate( + ( !$side ? $boundings->x0 : $labelPosition->x + $symbolSize ), + $minHeight + ), + ( !$side ? $labelPosition->x - $boundings->x0 - $symbolSize : $boundings->x1 - $labelPosition->x - $symbolSize ), + $labelHeight, + ( !$side ? ezcGraph::RIGHT : ezcGraph::LEFT ) | ezcGraph::MIDDLE + ) + ); + + // Add used space to minHeight + $minHeight += $labelHeight; + } + } + } + + /** + * Draws the collected circle sectors + * + * All circle sectors are collected and drawn later to be able to render + * the shadows of the pie segments in the back of all pie segments, and + * ensure the correct drawing order for all pie segment elements. + * + * @return void + */ + protected function finishCirleSectors() + { + $zBuffer = array(); + + $shadows = array(); + $shadowCenter = false; + $shadowEndAngle = false; + + // Add circle sector sides to simple z buffer prioriry list + foreach ( $this->circleSectors as $circleSector ) + { + // Draw shadow if wanted + if ( $this->options->pieChartShadowSize > 0 ) + { + if ( $shadowEndAngle === false ) + { + $shadowStartAngle = $circleSector['start']; + $shadowEndAngle = $circleSector['end']; + $shadowCenter = $circleSector['center']; + } + elseif ( $circleSector['center'] == $shadowCenter ) + { + $shadowEndAngle = $circleSector['end']; + } + else + { + $shadows[] = array( + 'center' => $shadowCenter, + 'start' => $shadowStartAngle, + 'end' => $shadowEndAngle, + 'width' => $circleSector['width'], + 'height' => $circleSector['height'], + ); + + $shadowCenter = $circleSector['center']; + $shadowStartAngle = $circleSector['start']; + $shadowEndAngle = $circleSector['end']; + } + } + + $darkenedColor = $circleSector['color']->darken( $this->options->dataBorder ); + + $center = (int) ( $circleSector['center']->y + sin( deg2rad( $circleSector['start'] + ( $circleSector['end'] - $circleSector['start'] ) / 2 ) ) * $circleSector['height'] / 2 + $this->options->pieChartHeight / 2 + 1 ); + + $zBuffer[$center][] = array( + 'method' => 'drawCircularArc', + 'paramenters' => array( + $circleSector['center'], + $circleSector['width'], + $circleSector['height'], + $this->options->pieChartHeight, + $circleSector['start'], + $circleSector['end'], + $circleSector['color'] + ) + ); + + // Left side + $polygonPoints = array( + $circleSector['center'], + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y + $this->options->pieChartHeight + ), + new ezcGraphCoordinate( + $circleSector['center']->x + cos( deg2rad( $circleSector['start'] ) ) * $circleSector['width'] / 2, + $circleSector['center']->y + sin( deg2rad( $circleSector['start'] ) ) * $circleSector['height'] / 2 + $this->options->pieChartHeight + ), + new ezcGraphCoordinate( + $circleSector['center']->x + cos( deg2rad( $circleSector['start'] ) ) * $circleSector['width'] / 2, + $circleSector['center']->y + sin( deg2rad( $circleSector['start'] ) ) * $circleSector['height'] / 2 + ), + ); + + // Get average y coordinate for polygon to use for zBuffer + $center = 0; + foreach ( $polygonPoints as $point ) + { + $center += $point->y; + } + $center = (int) ( $center / count( $polygonPoints ) ); + + $zBuffer[$center][] = array( + 'method' => 'drawPolygon', + 'paramenters' => array( + $polygonPoints, + $circleSector['color'], + true + ), + ); + + $zBuffer[$center][] = array( + 'method' => 'drawPolygon', + 'paramenters' => array( + $polygonPoints, + $darkenedColor, + false + ), + ); + + // Right side + $polygonPoints = array( + $circleSector['center'], + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y + $this->options->pieChartHeight + ), + new ezcGraphCoordinate( + $circleSector['center']->x + cos( deg2rad( $circleSector['end'] ) ) * $circleSector['width'] / 2, + $circleSector['center']->y + sin( deg2rad( $circleSector['end'] ) ) * $circleSector['height'] / 2 + $this->options->pieChartHeight + ), + new ezcGraphCoordinate( + $circleSector['center']->x + cos( deg2rad( $circleSector['end'] ) ) * $circleSector['width'] / 2, + $circleSector['center']->y + sin( deg2rad( $circleSector['end'] ) ) * $circleSector['height'] / 2 + ), + ); + + // Get average y coordinate for polygon to use for zBuffer + $center = 0; + foreach ( $polygonPoints as $point ) + { + $center += $point->y; + } + $center = (int) ( $center / count( $polygonPoints ) ); + + $zBuffer[$center][] = array( + 'method' => 'drawPolygon', + 'paramenters' => array( + $polygonPoints, + $circleSector['color'], + true + ), + ); + + $zBuffer[$center][] = array( + 'method' => 'drawPolygon', + 'paramenters' => array( + $polygonPoints, + $darkenedColor, + false + ), + ); + } + + if ( $this->options->pieChartShadowSize > 0 ) + { + $shadows[] = array( + 'center' => $shadowCenter, + 'start' => $shadowStartAngle, + 'end' => $shadowEndAngle, + 'width' => $circleSector['width'], + 'height' => $circleSector['height'], + ); + } + + // Draw collected shadows + foreach ( $shadows as $circleSector ) + { + for ( $i = $this->options->pieChartShadowSize; $i > 0; --$i ) + { + $startAngle = $circleSector['start']; + $endAngle = $circleSector['end']; + + $startAngle = $circleSector['start'] - ( $this->options->pieChartShadowSize - $i ); + $endAngle = $circleSector['end'] + ( $this->options->pieChartShadowSize - $i ); + + if ( ( $endAngle - $startAngle ) >= 360 ) + { + $this->driver->drawCircle( + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y + $this->options->pieChartHeight + ), + $circleSector['width'] + $i * 2, + $circleSector['height'] + $i * 2, + $this->options->pieChartShadowColor->transparent( 1 - ( $this->options->pieChartShadowTransparency / $this->options->pieChartShadowSize ) ), + true + ); + } + else + { + $this->driver->drawCircleSector( + new ezcGraphCoordinate( + $circleSector['center']->x, + $circleSector['center']->y + $this->options->pieChartHeight + ), + $circleSector['width'] + $i * 2, + $circleSector['height'] + $i * 2, + $startAngle, + $endAngle, + $this->options->pieChartShadowColor->transparent( 1 - ( $this->options->pieChartShadowTransparency / $this->options->pieChartShadowSize ) ), + true + ); + } + } + } + + ksort( $zBuffer ); + foreach ( $zBuffer as $sides ) + { + foreach ( $sides as $side ) + { + call_user_func_array( array( $this->driver, $side['method'] ), $side['paramenters'] ); + } + } + + // Draw circle sector for front + foreach ( $this->circleSectors as $circleSector ) + { + $this->addElementReference( $circleSector['context'], + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'], + $circleSector['height'], + $circleSector['start'], + $circleSector['end'], + $circleSector['color'], + true + ) + ); + + if ( $this->options->pieChartGleam !== false ) + { + $gradient = new ezcGraphLinearGradient( + $circleSector['center'], + new ezcGraphCoordinate( + $circleSector['center']->x - $circleSector['width'] / 2, + $circleSector['center']->y - $circleSector['height'] / 2 + ), + $this->options->pieChartGleamColor->transparent( 1 ), + $this->options->pieChartGleamColor->transparent( $this->options->pieChartGleam ) + ); + + $this->addElementReference( $circleSector['context'], + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'] - $this->options->pieChartGleamBorder * 2, + $circleSector['height'] - $this->options->pieChartGleamBorder * 2 * $this->options->pieChartRotation, + $circleSector['start'], + $circleSector['end'], + $gradient, + true + ) + ); + } + + $darkenedColor = $circleSector['color']->darken( $this->options->dataBorder ); + $this->driver->drawCircleSector( + $circleSector['center'], + $circleSector['width'], + $circleSector['height'], + $circleSector['start'], + $circleSector['end'], + $darkenedColor, + false + ); + + if ( $this->options->pieChartGleam !== false ) + { + $radialGradient = new ezcGraphRadialGradient( + new ezcGraphCoordinate( + $circleSector['center']->x + $circleSector['width'] / 2 * cos( deg2rad( 135 ) ), + $circleSector['center']->y + $circleSector['height'] / 2 * sin( deg2rad( 135 ) ) + ), + $circleSector['width'], + $circleSector['height'], + $this->options->pieChartGleamColor->transparent( $this->options->pieChartGleam ), + $this->options->pieChartGleamColor->transparent( .8 ) + ); + + $this->driver->drawCircularArc( + $circleSector['center'], + $circleSector['width'], + $circleSector['height'], + 0, + $circleSector['start'], + $circleSector['end'], + $radialGradient, + false + ); + } + } + } + + /** + * Draw collected front lines + * + * Draw all grid and axis lines, which should be redrawn in front of the + * data. + * + * @return void + */ + protected function finishFrontLines() + { + foreach ( $this->frontLines as $line ) + { + $this->driver->drawLine( + $line[0], + $line[1], + $line[2], + $line[3] + ); + } + } + + /** + * Draw the collected line symbols + * + * Symbols for the data lines are collected and delayed to ensure that + * they are not covered and hidden by other data lines. + * + * @return void + */ + protected function finishLineSymbols() + { + foreach ( $this->linePostSymbols as $symbol ) + { + $this->addElementReference( $symbol['context'], + $this->drawSymbol( + $symbol['boundings'], + $symbol['color'], + $symbol['symbol'] + ) + ); + } + } + + /** + * Draws a bar with a rectangular ground shape. + * + * @param ezcGraphContext $context + * @param ezcGraphColor $color + * @param ezcGraphCoordinate $position + * @param float $barWidth + * @param float $offset + * @param float $axisPosition + * @param float $startDepth + * @param float $midDepth + * @param float $endDepth + * @return void + */ + protected function drawRectangularBar( + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $position, + $barWidth, + $offset, + $axisPosition, + $startDepth, + $midDepth, + $endDepth ) + { + $barPolygonArray = array( + new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset, + $this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ), + new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset, + $this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ), + new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth, + $this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ), + new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth, + $this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ), + ); + + // Draw right bar side + $this->barPostProcessing[] = array( + 'index' => $barPolygonArray[2]->x + ( 1 - $position->y ), + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + array( + $this->get3dCoordinate( $barPolygonArray[2], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $endDepth ), + $this->get3dCoordinate( $barPolygonArray[2], $endDepth ), + ), + $color->darken( $this->options->barDarkenSide ), + true + ), + ); + + // Draw top side + $this->barPostProcessing[] = array( + 'index' => $barPolygonArray[1]->x + ( 1 - $position->y ), + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + ( $barPolygonArray[1]->y < $barPolygonArray[3]->y + ? array( + $this->get3dCoordinate( $barPolygonArray[1], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[2], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[2], $endDepth ), + $this->get3dCoordinate( $barPolygonArray[1], $endDepth ), + ) + : array( + $this->get3dCoordinate( $barPolygonArray[0], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $endDepth ), + $this->get3dCoordinate( $barPolygonArray[0], $endDepth ), + ) + ), + $color->darken( $this->options->barDarkenTop ), + true + ), + ); + + // Draw top side gleam + if ( $this->options->barChartGleam !== false ) + { + $this->barPostProcessing[] = array( + 'index' => $barPolygonArray[1]->x + 1 + ( 1 - $position->y ), + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + ( $barPolygonArray[1]->y < $barPolygonArray[3]->y + ? array( + $this->get3dCoordinate( $barPolygonArray[1], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[2], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[2], $endDepth ), + $this->get3dCoordinate( $barPolygonArray[1], $endDepth ), + ) + : array( + $this->get3dCoordinate( $barPolygonArray[0], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $endDepth ), + $this->get3dCoordinate( $barPolygonArray[0], $endDepth ), + ) + ), + new ezcGraphLinearGradient( + ( $barPolygonArray[1]->y < $barPolygonArray[3]->y + ? $this->get3dCoordinate( $barPolygonArray[2], $endDepth ) + : $this->get3dCoordinate( $barPolygonArray[3], $endDepth ) + ), + ( $barPolygonArray[1]->y < $barPolygonArray[3]->y + ? $this->get3dCoordinate( $barPolygonArray[1], $startDepth ) + : $this->get3dCoordinate( $barPolygonArray[0], $startDepth ) + ), + ezcGraphColor::fromHex( '#FFFFFFFF' ), + ezcGraphColor::fromHex( '#FFFFFF' )->transparent( 1 - $this->options->barChartGleam ) + ), + true + ), + ); + } + + // Draw front side + $this->barPostProcessing[] = array( + 'index' => $barPolygonArray[1]->x + ( 1 - $position->y ), + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + array( + $this->get3dCoordinate( $barPolygonArray[0], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[1], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[2], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $startDepth ), + ), + $color, + true + ), + ); + + // Draw front side gleam + if ( $this->options->barChartGleam !== false ) + { + $this->barPostProcessing[] = array( + 'index' => $barPolygonArray[1]->x + 1 + ( 1 - $position->y ), + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + array( + $this->get3dCoordinate( $barPolygonArray[0], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[1], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[2], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[3], $startDepth ), + ), + new ezcGraphLinearGradient( + $this->get3dCoordinate( $barPolygonArray[3], $startDepth ), + $this->get3dCoordinate( $barPolygonArray[1], $startDepth ), + ezcGraphColor::fromHex( '#FFFFFFFF' ), + ezcGraphColor::fromHex( '#FFFFFF' )->transparent( 1 - $this->options->barChartGleam ) + ), + true + ), + ); + } + } + + /** + * Draws a bar with a diamond ground shape. + * + * @param ezcGraphContext $context + * @param ezcGraphColor $color + * @param ezcGraphCoordinate $position + * @param float $barWidth + * @param float $offset + * @param float $axisPosition + * @param float $startDepth + * @param float $midDepth + * @param float $endDepth + * @return void + */ + protected function drawDiamondBar( + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $position, + $barWidth, + $offset, + $axisPosition, + $startDepth, + $midDepth, + $endDepth ) + { + $barCoordinateArray = array( + // The bottom point of the diamond is moved to .7 instead + // of .5 because it looks more correct, even it is wrong... + 'x' => array( + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset, + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth * .7, + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth, + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth * .3, + ), + 'y' => array( + $this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ), + $this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ), + ), + ); + + // Left side + $this->barPostProcessing[] = array( + 'index' => $barCoordinateArray['x'][0], + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + array( + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $barCoordinateArray['y'][0] ), $midDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $barCoordinateArray['y'][1] ), $midDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][1] ), $startDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][0] ), $startDepth ), + ), + $color, + true + ), + ); + + // Right side + $this->barPostProcessing[] = array( + 'index' => $barCoordinateArray['x'][1], + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + array( + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $barCoordinateArray['y'][0] ), $midDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $barCoordinateArray['y'][1] ), $midDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][1] ), $startDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $barCoordinateArray['y'][0] ), $startDepth ), + ), + $color->darken( $this->options->barDarkenSide ), + true + ), + ); + + $topLocation = min( + $barCoordinateArray['y'][0], + $barCoordinateArray['y'][1] + ); + + // Top side + $this->barPostProcessing[] = array( + 'index' => $barCoordinateArray['x'][0], + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + array( + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $topLocation ), $startDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $topLocation ), $midDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][3], $topLocation ), $endDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $topLocation ), $midDepth ), + ), + $color->darken( $this->options->barDarkenTop ), + true + ), + ); + + // Top side gleam + if ( $this->options->barChartGleam !== false ) + { + $this->barPostProcessing[] = array( + 'index' => $barCoordinateArray['x'][0] + 1, + 'method' => 'drawPolygon', + 'context' => $context, + 'parameters' => array( + array( + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][1], $topLocation ), $startDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $topLocation ), $midDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][3], $topLocation ), $endDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $topLocation ), $midDepth ), + ), + new ezcGraphLinearGradient( + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][2], $topLocation ), $midDepth ), + $this->get3dCoordinate( new ezcGraphCoordinate( $barCoordinateArray['x'][0], $topLocation ), $midDepth ), + ezcGraphColor::fromHex( '#FFFFFFFF' ), + ezcGraphColor::fromHex( '#FFFFFF' )->transparent( 1 - $this->options->barChartGleam ) + ), + true + ), + ); + } + } + + /** + * Draws a bar with a circular ground shape. + * + * @param ezcGraphContext $context + * @param ezcGraphColor $color + * @param ezcGraphCoordinate $position + * @param float $barWidth + * @param float $offset + * @param float $axisPosition + * @param float $startDepth + * @param float $midDepth + * @param float $endDepth + * @param int $symbol + * @return void + */ + protected function drawCircularBar( + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $position, + $barWidth, + $offset, + $axisPosition, + $startDepth, + $midDepth, + $endDepth, + $symbol ) + { + $barCenterTop = new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth / 2, + $this->dataBoundings->y0 + $this->yAxisSpace + $position->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + + ); + $barCenterBottom = new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $position->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ) + $offset + $barWidth / 2, + $this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ); + + if ( $barCenterTop->y > $barCenterBottom->y ) + { + $tmp = $barCenterTop; + $barCenterTop = $barCenterBottom; + $barCenterBottom = $tmp; + } + + $this->barPostProcessing[] = array( + 'index' => $barCenterBottom->x, + 'method' => 'drawCircularArc', + 'context' => $context, + 'parameters' => array( + $this->get3dCoordinate( $barCenterTop, $midDepth ), + $barWidth, + $barWidth / 2, + ( $barCenterBottom->y - $barCenterTop->y ) * $this->yDepthFactor, + 0, + 180, + $color + ), + ); + + $this->barPostProcessing[] = array( + 'index' => $barCenterBottom->x + 1, + 'method' => 'drawCircle', + 'context' => $context, + 'parameters' => array( + $top = $this->get3dCoordinate( $barCenterTop, $midDepth ), + $barWidth, + $barWidth / 2, + ( $symbol === ezcGraph::CIRCLE + ? new ezcGraphLinearGradient( + new ezcGraphCoordinate( + $top->x - $barWidth / 2, + $top->y + ), + new ezcGraphCoordinate( + $top->x + $barWidth / 2, + $top->y + ), + $color->darken( $this->options->barDarkenTop ), + $color + ) + : $color + ) + ), + ); + } + + /** + * Draw bar + * + * Draws a bar as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $position Position of data point + * @param float $stepSize Space which can be used for bars + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param int $symbol Symbol to draw for line + * @param float $axisPosition Position of axis for drawing filled lines + * @return void + */ + public function drawBar( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $position, + $stepSize, + $dataNumber = 1, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + $axisPosition = 0. ) + { + // Apply margin + $margin = $stepSize * $this->options->barMargin; + $padding = $stepSize * $this->options->barPadding; + $barWidth = ( $stepSize - $margin ) / $dataCount - $padding; + $offset = - $stepSize / 2 + $margin / 2 + ( $dataCount - $dataNumber - 1 ) * ( $padding + $barWidth ) + $padding / 2; + + if ( $barWidth < 0 ) + { + $offset -= $barWidth = abs( $barWidth ); + } + + $startDepth = $this->options->barMargin; + $midDepth = .5; + $endDepth = 1 - $this->options->barMargin; + + switch ( $symbol ) + { + case ezcGraph::NO_SYMBOL: + $this->drawRectangularBar( + $context, + $color, + $position, + $barWidth, + $offset, + $axisPosition, + $startDepth, + $midDepth, + $endDepth + ); + break; + case ezcGraph::DIAMOND: + $this->drawDiamondBar( + $context, + $color, + $position, + $barWidth, + $offset, + $axisPosition, + $startDepth, + $midDepth, + $endDepth + ); + break; + case ezcGraph::BULLET: + case ezcGraph::CIRCLE: + $this->drawCircularBar( + $context, + $color, + $position, + $barWidth, + $offset, + $axisPosition, + $startDepth, + $midDepth, + $endDepth, + $symbol + ); + break; + } + } + + /** + * Draw stacked bar + * + * Draws a stacked bar part as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $start + * @param ezcGraphCoordinate $position + * @param float $stepSize Space which can be used for bars + * @param int $symbol Symbol to draw for line + * @param float $axisPosition Position of axis for drawing filled lines + * @return void + */ + public function drawStackedBar( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $start, + ezcGraphCoordinate $position, + $stepSize, + $symbol = ezcGraph::NO_SYMBOL, + $axisPosition = 0. ) + { + // Apply margin + $margin = $stepSize * $this->options->barMargin; + $barWidth = $stepSize - $margin; + $offset = - $stepSize / 2 + $margin / 2; + + if ( $barWidth < 0 ) + { + $offset -= $barWidth = abs( $barWidth ); + } + + $startDepth = $this->options->barMargin; + $midDepth = .5; + $endDepth = 1 - $this->options->barMargin; + + switch ( $symbol ) + { + case ezcGraph::NO_SYMBOL: + case ezcGraph::DIAMOND: + case ezcGraph::BULLET: + case ezcGraph::CIRCLE: + $this->drawRectangularBar( + $context, + $color, + $position, + $barWidth, + $offset, + $start->y, + $startDepth, + $midDepth, + $endDepth + ); + break; + } + } + + /** + * Draw all collected bar elements + * + * Draw all collected bar elements after sorting them depending of their + * position to simulate simple z buffering. + * + * @access protected + * @return void + */ + protected function finishBars() + { + if ( !count( $this->barPostProcessing ) ) + { + return true; + } + + $zIndexArray = array(); + foreach ( $this->barPostProcessing as $key => $barPolygon ) + { + $zIndexArray[$key] = $barPolygon['index']; + } + + array_multisort( + $zIndexArray, SORT_ASC, SORT_NUMERIC, + $this->barPostProcessing + ); + + foreach ( $this->barPostProcessing as $bar ) + { + $this->addElementReference( $bar['context'], + call_user_func_array( + array( $this->driver, $bar['method'] ), + $bar['parameters'] + ) + ); + } + } + + /** + * Draw data line + * + * Draws a line as a data element in a line chart + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphColor $color Color of line + * @param ezcGraphCoordinate $start Starting point + * @param ezcGraphCoordinate $end Ending point + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param int $symbol Symbol to draw for line + * @param ezcGraphColor $symbolColor Color of the symbol, defaults to linecolor + * @param ezcGraphColor $fillColor Color to fill line with + * @param float $axisPosition Position of axis for drawing filled lines + * @param float $thickness Line thickness + * @return void + */ + public function drawDataLine( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphColor $color, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + $dataNumber = 0, + $dataCount = 1, + $symbol = ezcGraph::NO_SYMBOL, + ezcGraphColor $symbolColor = null, + ezcGraphColor $fillColor = null, + $axisPosition = 0., + $thickness = 1. ) + { + // Calculate line width based on options + if ( $this->options->seperateLines ) + { + $startDepth = ( 1 / $dataCount ) * $dataNumber; + $endDepth = ( 1 / $dataCount ) * ( $dataNumber + 1 ); + } + else + { + $startDepth = false; + $endDepth = true; + } + + // Determine Coordinates depending on boundings and data point position + $startCoord = new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $start->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ), + $this->dataBoundings->y0 + $this->yAxisSpace + $start->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ); + $endCoord = new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $end->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ), + $this->dataBoundings->y0 + $this->yAxisSpace + $end->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ); + + // 3D-fy coordinates + $linePolygonPoints = array( + $this->get3dCoordinate( $startCoord, $startDepth ), + $this->get3dCoordinate( $endCoord, $startDepth ), + $this->get3dCoordinate( $endCoord, $endDepth ), + $this->get3dCoordinate( $startCoord, $endDepth ), + ); + + $startAxisCoord = new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $start->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ), + $this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ); + $endAxisCoord = new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $end->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ), + $this->dataBoundings->y0 + $this->yAxisSpace + $axisPosition * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ); + + // 3D-fy coordinates + $axisPolygonPoints = array( + $this->get3dCoordinate( $startAxisCoord, $startDepth ), + $this->get3dCoordinate( $endAxisCoord, $startDepth ), + $this->get3dCoordinate( $endAxisCoord, $endDepth ), + $this->get3dCoordinate( $startAxisCoord, $endDepth ), + ); + + // Perhaps fill up line + if ( $fillColor !== null && + $start->x != $end->x ) + { + $startValue = $axisPosition - $start->y; + $endValue = $axisPosition - $end->y; + + if ( ( $startValue == 0 ) || + ( $endValue == 0 ) || + ( $startValue / abs( $startValue ) == $endValue / abs( $endValue ) ) ) + { + // Values have the same sign or are on the axis + $this->driver->drawPolygon( + array( + $linePolygonPoints[0], + $linePolygonPoints[1], + $this->get3dCoordinate( $endAxisCoord, $startDepth ), + $this->get3dCoordinate( $startAxisCoord, $startDepth ), + ), + $fillColor, + true + ); + } + else + { + // values are on differente sides of the axis - split the filled polygon + $startDiff = abs( $axisPosition - $start->y ); + $endDiff = abs( $axisPosition - $end->y ); + + $cuttingPosition = $startDiff / ( $endDiff + $startDiff ); + $cuttingPoint = new ezcGraphCoordinate( + $startCoord->x + ( $endCoord->x - $startCoord->x ) * $cuttingPosition, + $startAxisCoord->y + ); + + $this->driver->drawPolygon( + array( + $this->get3dCoordinate( $startAxisCoord, $startDepth ), + $linePolygonPoints[0], + $this->get3dCoordinate( $cuttingPoint, $startDepth ), + ), + $fillColor, + true + ); + + $this->driver->drawPolygon( + array( + $this->get3dCoordinate( $endAxisCoord, $startDepth ), + $linePolygonPoints[1], + $this->get3dCoordinate( $cuttingPoint, $startDepth ), + ), + $fillColor, + true + ); + } + + // Draw closing foo + $this->driver->drawPolygon( + array( + $linePolygonPoints[2], + $linePolygonPoints[1], + $this->get3dCoordinate( $endAxisCoord, $startDepth ), + $this->get3dCoordinate( $endAxisCoord, $endDepth ), + ), + $fillColor, + true + ); + } + + + // Draw line + $this->driver->drawPolygon( + $linePolygonPoints, + $color, + true, + $thickness + ); + + // Draw polygon border + if ( $this->options->dataBorder > 0 ) + { + $this->driver->drawPolygon( + $linePolygonPoints, + $color->darken( $this->options->dataBorder ), + false, + $thickness + ); + } + + // Draw line symbol + if ( $this->options->showSymbol && + ( $symbol !== ezcGraph::NO_SYMBOL ) ) + { + if ( $symbolColor === null ) + { + $symbolColor = $color; + } + + $this->linePostSymbols[] = array( + 'boundings' => new ezcGraphBoundings( + $linePolygonPoints[2]->x - $this->options->symbolSize / 2, + $linePolygonPoints[2]->y - $this->options->symbolSize / 2, + $linePolygonPoints[2]->x + $this->options->symbolSize / 2, + $linePolygonPoints[2]->y + $this->options->symbolSize / 2 + ), + 'color' => $symbolColor, + 'context' => $context, + 'symbol' => $symbol, + ); + } + } + + /** + * Draws a highlight textbox for a datapoint. + * + * A highlight textbox for line and bar charts means a box with the current + * value in the graph. + * + * @param ezcGraphBoundings $boundings Chart boundings + * @param ezcGraphContext $context Context of call + * @param ezcGraphCoordinate $end Ending point + * @param float $axisPosition Position of axis for drawing filled lines + * @param int $dataNumber Number of dataset + * @param int $dataCount Count of datasets in chart + * @param ezcGraphFontOptions $font Font used for highlight string + * @param string $text Acutual value + * @param int $size Size of highlight text + * @param ezcGraphColor $markLines + * @return void + */ + public function drawDataHighlightText( + ezcGraphBoundings $boundings, + ezcGraphContext $context, + ezcGraphCoordinate $end, + $axisPosition = 0., + $dataNumber = 1, + $dataCount = 1, + ezcGraphFontOptions $font, + $text, + $size, + ezcGraphColor $markLines = null ) + { + $this->driver->options->font = $font; + $width = $this->dataBoundings->width / $dataCount; + + // Calculate line width based on options + if ( $this->options->seperateLines ) + { + $endDepth = ( 1 / $dataCount ) * ( $dataNumber + 1 ); + } + else + { + $endDepth = true; + } + + $dataPoint = new ezcGraphCoordinate( + $this->dataBoundings->x0 + $this->xAxisSpace + $end->x * ( $this->dataBoundings->x1 - ( $this->dataBoundings->x0 + 2 * $this->xAxisSpace ) ), + $this->dataBoundings->y0 + $this->yAxisSpace + $end->y * ( $this->dataBoundings->y1 - ( $this->dataBoundings->y0 + 2 * $this->yAxisSpace ) ) + ); + + if ( $end->y < $axisPosition ) + { + $this->driver->drawTextBox( + $text, + $this->get3dCoordinate( new ezcGraphCoordinate( + $dataPoint->x - $width / 2, + $dataPoint->y - $size - $font->padding - $this->options->symbolSize + ), $endDepth ), + $width * $this->xDepthFactor, + $size, + ezcGraph::CENTER | ezcGraph::BOTTOM + ); + } + else + { + $this->driver->drawTextBox( + $text, + $this->get3dCoordinate( new ezcGraphCoordinate( + $dataPoint->x - $width / 2, + $dataPoint->y + $font->padding + $this->options->symbolSize + ), $endDepth ), + $width * $this->xDepthFactor, + $size, + ezcGraph::CENTER | ezcGraph::TOP + ); + } + } + + /** + * Draw legend + * + * Will draw a legend in the bounding box + * + * @param ezcGraphBoundings $boundings Bounding of legend + * @param ezcGraphChartElementLegend $legend Legend to draw; + * @param int $type Type of legend: Protrait or landscape + * @return void + */ + public function drawLegend( + ezcGraphBoundings $boundings, + ezcGraphChartElementLegend $legend, + $type = ezcGraph::VERTICAL ) + { + $labels = $legend->labels; + + // Calculate boundings of each label + if ( $type & ezcGraph::VERTICAL ) + { + $labelWidth = $boundings->x1 - $boundings->x0; + $labelHeight = min( + ( $boundings->y1 - $boundings->y0 ) / count( $labels ) - $legend->spacing, + $legend->symbolSize + 2 * $legend->padding + ); + } + else + { + $labelWidth = ( $boundings->x1 - $boundings->x0 ) / count( $labels ) - $legend->spacing; + $labelHeight = min( + $boundings->height, + $legend->symbolSize + 2 * $legend->padding + ); + } + + $symbolSize = $labelHeight - 2 * $legend->padding; + + // Draw all labels + $labelPosition = new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ); + foreach ( $labels as $label ) + { + $this->elements['legend_url'][$label['label']] = $label['url']; + + $this->elements['legend'][$label['label']]['symbol'] = $this->drawSymbol( + new ezcGraphBoundings( + $labelPosition->x + $legend->padding, + $labelPosition->y + $legend->padding, + $labelPosition->x + $legend->padding + $symbolSize, + $labelPosition->y + $legend->padding + $symbolSize + ), + $label['color'], + $label['symbol'] + ); + + $this->elements['legend'][$label['label']]['text'] = $this->driver->drawTextBox( + $label['label'], + new ezcGraphCoordinate( + $labelPosition->x + 2 * $legend->padding + $symbolSize, + $labelPosition->y + $legend->padding + ), + $labelWidth - $symbolSize - 3 * $legend->padding, + $labelHeight - 2 * $legend->padding, + ezcGraph::LEFT | ezcGraph::MIDDLE + ); + + $labelPosition->x += ( $type === ezcGraph::VERTICAL ? 0 : $labelWidth + $legend->spacing ); + $labelPosition->y += ( $type === ezcGraph::VERTICAL ? $labelHeight + $legend->spacing : 0 ); + } + } + + /** + * Draw box + * + * Box are wrapping each major chart element and draw border, background + * and title to each chart element. + * + * Optionally a padding and margin for each box can be defined. + * + * @param ezcGraphBoundings $boundings Boundings of the box + * @param ezcGraphColor $background Background color + * @param ezcGraphColor $borderColor Border color + * @param int $borderWidth Border width + * @param int $margin Margin + * @param int $padding Padding + * @param mixed $title Title of the box + * @param int $titleSize Size of title in the box + * @return ezcGraphBoundings Remaining inner boundings + */ + public function drawBox( + ezcGraphBoundings $boundings, + ezcGraphColor $background = null, + ezcGraphColor $borderColor = null, + $borderWidth = 0, + $margin = 0, + $padding = 0, + $title = false, + $titleSize = 16 ) + { + // Apply margin + $boundings->x0 += $margin; + $boundings->y0 += $margin; + $boundings->x1 -= $margin; + $boundings->y1 -= $margin; + + if ( $background instanceof ezcGraphColor ) + { + // Draw box background + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + new ezcGraphCoordinate( $boundings->x0, $boundings->y1 ), + ), + $background, + true + ); + } + + if ( ( $borderColor instanceof ezcGraphColor ) && + ( $borderWidth > 0 ) ) + { + // Draw border + $this->driver->drawPolygon( + array( + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y0 ), + new ezcGraphCoordinate( $boundings->x1, $boundings->y1 ), + new ezcGraphCoordinate( $boundings->x0, $boundings->y1 ), + ), + $borderColor, + false, + $borderWidth + ); + + // Reduce local boundings by borderWidth + $boundings->x0 += $borderWidth; + $boundings->y0 += $borderWidth; + $boundings->x1 -= $borderWidth; + $boundings->y1 -= $borderWidth; + } + + // Apply padding + $boundings->x0 += $padding; + $boundings->y0 += $padding; + $boundings->x1 -= $padding; + $boundings->y1 -= $padding; + + // Add box title + if ( $title !== false ) + { + switch ( $this->options->titlePosition ) + { + case ezcGraph::TOP: + $this->driver->drawTextBox( + $title, + new ezcGraphCoordinate( $boundings->x0, $boundings->y0 ), + $boundings->x1 - $boundings->x0, + $titleSize, + $this->options->titleAlignement + ); + + $boundings->y0 += $titleSize + $padding; + $boundings->y1 -= $titleSize + $padding; + break; + case ezcGraph::BOTTOM: + $this->driver->drawTextBox( + $title, + new ezcGraphCoordinate( $boundings->x0, $boundings->y1 - $titleSize ), + $boundings->x1 - $boundings->x0, + $titleSize, + $this->options->titleAlignement + ); + + $boundings->y1 -= $titleSize + $padding; + break; + } + } + + return $boundings; + } + + /** + * Draw text + * + * Draws the provided text in the boundings + * + * @param ezcGraphBoundings $boundings Boundings of text + * @param string $text Text + * @param int $align Alignement of text + * @param ezcGraphRotation $rotation + * @return void + */ + public function drawText( + ezcGraphBoundings $boundings, + $text, + $align = ezcGraph::LEFT, + ezcGraphRotation $rotation = null ) + { + if ( $this->depth === false ) + { + // We are not 3d for now, wg. rendering normal text boxes like the + // title + $topleft = new ezcGraphCoordinate( + $boundings->x0, + $boundings->y0 + ); + $bottomright = new ezcGraphCoordinate( + $boundings->x1, + $boundings->y1 + ); + } + else + { + // The 3d part started + $topleft = $this->get3dCoordinate( + new ezcGraphCoordinate( + $boundings->x0, + $boundings->y0 + ), false + ); + $bottomright = $this->get3dCoordinate( + new ezcGraphCoordinate( + $boundings->x1, + $boundings->y1 + ), false + ); + + // Also modify rotation accordingly + if ( $rotation !== null ) + { + $rotation = new ezcGraphRotation( + $rotation->getRotation(), + $this->get3dCoordinate( $rotation->getCenter(), false ) + ); + } + } + + $this->driver->drawTextBox( + $text, + $topleft, + $bottomright->x - $topleft->x, + $bottomright->y - $topleft->y, + $align, + $rotation + ); + } + + /** + * Draw grid line + * + * Draw line for the grid in the chart background + * + * + * @param ezcGraphCoordinate $start Start point + * @param ezcGraphCoordinate $end End point + * @param ezcGraphColor $color Color of the grid line + * @return void + */ + public function drawGridLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color ) + { + $gridPolygonCoordinates = array( + $this->get3dCoordinate( $start, false ), + $this->get3dCoordinate( $end, false ), + $this->get3dCoordinate( $end, true ), + $this->get3dCoordinate( $start, true ), + ); + + // Draw grid polygon + if ( $this->options->fillGrid === 0 ) + { + $this->driver->drawLine( + $gridPolygonCoordinates[2], + $gridPolygonCoordinates[3], + $color + ); + } + else + { + if ( $this->options->fillGrid === 1 ) + { + $this->driver->drawPolygon( + $gridPolygonCoordinates, + $color, + true + ); + } + else + { + $this->driver->drawPolygon( + $gridPolygonCoordinates, + $color->transparent( $this->options->fillGrid ), + true + ); + } + + // Draw grid lines - scedule some for later to be drawn in front of + // the data + $this->frontLines[] = array( + $gridPolygonCoordinates[0], + $gridPolygonCoordinates[1], + $color, + 1 + ); + + $this->frontLines[] = array( + $gridPolygonCoordinates[1], + $gridPolygonCoordinates[2], + $color, + 1 + ); + + $this->driver->drawLine( + $gridPolygonCoordinates[2], + $gridPolygonCoordinates[3], + $color, + 1 + ); + + $this->frontLines[] = array( + $gridPolygonCoordinates[3], + $gridPolygonCoordinates[0], + $color, + 1 + ); + } + } + + /** + * Draw step line + * + * Draw a step (marker for label position) on a axis. + * + * @param ezcGraphCoordinate $start Start point + * @param ezcGraphCoordinate $end End point + * @param ezcGraphColor $color Color of the grid line + * @return void + */ + public function drawStepLine( ezcGraphCoordinate $start, ezcGraphCoordinate $end, ezcGraphColor $color ) + { + $stepPolygonCoordinates = array( + $this->get3dCoordinate( $start, true ), + $this->get3dCoordinate( $end, true ), + $this->get3dCoordinate( $end, false ), + $this->get3dCoordinate( $start, false ), + ); + + // Draw step polygon + if ( ( $this->options->fillAxis > 0 ) && + ( $this->options->fillAxis < 1 ) ) + { + $this->driver->drawPolygon( + $stepPolygonCoordinates, + $color->transparent( $this->options->fillAxis ), + true + ); + + $this->driver->drawPolygon( + $stepPolygonCoordinates, + $color, + false + ); + } + else + { + $this->driver->drawPolygon( + $stepPolygonCoordinates, + $color, + ! (bool) $this->options->fillAxis + ); + } + } + + /** + * Draw axis + * + * Draws an axis form the provided start point to the end point. A specific + * angle of the axis is not required. + * + * For the labeleing of the axis a sorted array with major steps and an + * array with minor steps is expected, which are build like this: + * array( + * array( + * 'position' => (float), + * 'label' => (string), + * ) + * ) + * where the label is optional. + * + * The label renderer class defines how the labels are rendered. For more + * documentation on this topic have a look at the basic label renderer + * class. + * + * Additionally it can be specified if a major and minor grid are rendered + * by defining a color for them. Teh axis label is used to add a caption + * for the axis. + * + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $start Start point of axis + * @param ezcGraphCoordinate $end Endpoint of axis + * @param ezcGraphChartElementAxis $axis Axis to render + * @param ezcGraphAxisLabelRenderer $labelClass Used label renderer + * @return void + */ + public function drawAxis( + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis, + ezcGraphAxisLabelRenderer $labelClass = null ) + { + // Calculate used space for three dimensional effects + if ( $this->depth === false ) + { + $this->depth = min( + ( $boundings->x1 - $boundings->x0 ) * $this->options->depth, + ( $boundings->y1 - $boundings->y0 ) * $this->options->depth + ); + + $this->xDepthFactor = 1 - $this->depth / ( $boundings->x1 - $boundings->x0 ); + $this->yDepthFactor = 1 - $this->depth / ( $boundings->y1 - $boundings->y0 ); + + $this->dataBoundings = clone $boundings; + } + + // Clone boundings to not be affected by internal mofifications + $boundings = clone $boundings; + + switch ( $axis->position ) + { + case ezcGraph::TOP: + case ezcGraph::BOTTOM: + $this->xAxisSpace = ( $this->dataBoundings->x1 - $this->dataBoundings->x0 ) * $axis->axisSpace; + break; + case ezcGraph::LEFT: + case ezcGraph::RIGHT: + $this->yAxisSpace = ( $this->dataBoundings->y1 - $this->dataBoundings->y0 ) * $axis->axisSpace; + break; + } + + // Determine normalized direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + $start->x += $boundings->x0; + $start->y += $boundings->y0; + $end->x += $boundings->x0; + $end->y += $boundings->y0; + + $axisPolygonCoordinates = array( + $this->get3dCoordinate( $start, true ), + $this->get3dCoordinate( $end, true ), + $this->get3dCoordinate( $end, false ), + $this->get3dCoordinate( $start, false ), + ); + + // Draw axis + if ( ( $this->options->fillAxis > 0 ) && + ( $this->options->fillAxis < 1 ) ) + { + $this->driver->drawPolygon( + $axisPolygonCoordinates, + $axis->border->transparent( $this->options->fillAxis ), + true + ); + } + else + { + $this->driver->drawPolygon( + $axisPolygonCoordinates, + $axis->border, + ! (bool) $this->options->fillAxis + ); + } + + // Draw axis lines - scedule some for later to be drawn in front of + // the data + $this->driver->drawLine( + $axisPolygonCoordinates[0], + $axisPolygonCoordinates[1], + $axis->border, + 1 + ); + + $this->frontLines[] = array( + $axisPolygonCoordinates[1], + $axisPolygonCoordinates[2], + $axis->border, + 1 + ); + + $this->frontLines[] = array( + $axisPolygonCoordinates[2], + $axisPolygonCoordinates[3], + $axis->border, + 1 + ); + + $this->frontLines[] = array( + $axisPolygonCoordinates[3], + $axisPolygonCoordinates[0], + $axis->border, + 1 + ); + + // Draw small arrowhead + $size = max( + $axis->minArrowHeadSize, + min( + $axis->maxArrowHeadSize, + abs( ceil( ( ( $end->x - $start->x ) + ( $end->y - $start->y ) ) * $axis->axisSpace / 4 ) ) + ) + ); + + $orthogonalDirection = clone $direction; + $orthogonalDirection->rotateClockwise(); + + $this->driver->drawPolygon( + array( + $axisPolygonCoordinates[1], + new ezcGraphCoordinate( + $axisPolygonCoordinates[1]->x + - $orthogonalDirection->x * $size / 2 + + $direction->x * $size, + $axisPolygonCoordinates[1]->y + - $orthogonalDirection->y * $size / 2 + + $direction->y * $size + ), + new ezcGraphCoordinate( + $axisPolygonCoordinates[1]->x + + $orthogonalDirection->x * $size / 2 + + $direction->x * $size, + $axisPolygonCoordinates[1]->y + + $orthogonalDirection->y * $size / 2 + + $direction->y * $size + ), + ), + $axis->border, + true + ); + + // Draw axis label + if ( $axis->label !== false ) + { + $width = $this->dataBoundings->x1 - $this->dataBoundings->x0; + switch ( $axis->position ) + { + case ezcGraph::TOP: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $axisPolygonCoordinates[2]->x + $axis->labelMargin - $width * ( 1 - $axis->axisSpace * 2 ), + $axisPolygonCoordinates[2]->y - $axis->labelMargin - $axis->labelSize + ), + $width * ( 1 - $axis->axisSpace * 2 ) - $axis->labelMargin, + $axis->labelSize, + ezcGraph::TOP | ezcGraph::RIGHT + ); + break; + case ezcGraph::BOTTOM: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $axisPolygonCoordinates[1]->x + $axis->labelMargin, + $axisPolygonCoordinates[1]->y + $axis->labelMargin + ), + $width * ( 1 - $axis->axisSpace * 2 ) - $axis->labelMargin, + $axis->labelSize, + ezcGraph::TOP | ezcGraph::LEFT + ); + break; + case ezcGraph::LEFT: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $axisPolygonCoordinates[1]->x - $width, + $axisPolygonCoordinates[1]->y - $axis->labelSize - $axis->labelMargin + ), + $width - $axis->labelMargin, + $axis->labelSize, + ezcGraph::BOTTOM | ezcGraph::RIGHT + ); + break; + case ezcGraph::RIGHT: + $this->driver->drawTextBox( + $axis->label, + new ezcGraphCoordinate( + $axisPolygonCoordinates[1]->x, + $axisPolygonCoordinates[1]->y - $axis->labelSize - $axis->labelMargin + ), + $width - $axis->labelMargin, + $axis->labelSize, + ezcGraph::BOTTOM | ezcGraph::LEFT + ); + break; + } + } + + // Collect axis labels and draw, when all axisSpaces are collected + $this->axisLabels[] = array( + 'object' => $labelClass, + 'boundings' => $boundings, + 'start' => clone $start, + 'end' => clone $end, + 'axis' => $axis, + ); + + if ( $this->xAxisSpace && $this->yAxisSpace ) + { + foreach ( $this->axisLabels as $axisLabel ) + { + // If font should not be synchronized, use font configuration from + // each axis + if ( $this->options->syncAxisFonts === false ) + { + $this->driver->options->font = $axisLabel['axis']->font; + } + + switch ( $axisLabel['axis']->position ) + { + case ezcGraph::RIGHT: + case ezcGraph::LEFT: + $axisLabel['start']->x += $this->xAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 ); + $axisLabel['end']->x -= $this->xAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 ); + break; + case ezcGraph::TOP: + case ezcGraph::BOTTOM: + $axisLabel['start']->y += $this->yAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 ); + $axisLabel['end']->y -= $this->yAxisSpace * ( $axisLabel['start'] > $axisLabel['end'] ? -1 : 1 ); + break; + } + + $axisLabel['object']->renderLabels( + $this, + $axisLabel['boundings'], + $axisLabel['start'], + $axisLabel['end'], + $axisLabel['axis'] + ); + } + } + } + + /** + * Draw background image + * + * Draws a background image at the defined position. If repeat is set the + * background image will be repeated like any texture. + * + * @param ezcGraphBoundings $boundings Boundings for the background image + * @param string $file Filename of background image + * @param int $position Position of background image + * @param int $repeat Type of repetition + * @return void + */ + public function drawBackgroundImage( + ezcGraphBoundings $boundings, + $file, + $position = 48, // ezcGraph::CENTER | ezcGraph::MIDDLE + $repeat = ezcGraph::NO_REPEAT ) + { + $imageData = getimagesize( $file ); + $imageWidth = $imageData[0]; + $imageHeight = $imageData[1]; + + $imageWidth = min( $imageWidth, $boundings->x1 - $boundings->x0 ); + $imageHeight = min( $imageHeight, $boundings->y1 - $boundings->y0 ); + + $imagePosition = new ezcGraphCoordinate( + $boundings->x0, + $boundings->y0 + ); + + // Determine x position + switch ( true ) { + case ( $repeat & ezcGraph::HORIZONTAL ): + // If is repeated on this axis fall back to position zero + case ( $position & ezcGraph::LEFT ): + $imagePosition->x = $boundings->x0; + break; + case ( $position & ezcGraph::RIGHT ): + $imagePosition->x = max( + $boundings->x1 - $imageWidth, + $boundings->x0 + ); + break; + default: + $imagePosition->x = max( + $boundings->x0 + ( $boundings->x1 - $boundings->x0 - $imageWidth ) / 2, + $boundings->x0 + ); + break; + } + + // Determine y position + switch ( true ) { + case ( $repeat & ezcGraph::VERTICAL ): + // If is repeated on this axis fall back to position zero + case ( $position & ezcGraph::TOP ): + $imagePosition->y = $boundings->y0; + break; + case ( $position & ezcGraph::BOTTOM ): + $imagePosition->y = max( + $boundings->y1 - $imageHeight, + $boundings->y0 + ); + break; + default: + $imagePosition->y = max( + $boundings->y0 + ( $boundings->y1 - $boundings->y0 - $imageHeight ) / 2, + $boundings->y0 + ); + break; + } + + // Texturize backround based on position and repetition + $position = new ezcGraphCoordinate( + $imagePosition->x, + $imagePosition->y + ); + + do + { + $position->y = $imagePosition->y; + + do + { + $this->driver->drawImage( + $file, + $position, + $imageWidth, + $imageHeight + ); + + $position->y += $imageHeight; + } + while ( ( $position->y < $boundings->y1 ) && + ( $repeat & ezcGraph::VERTICAL ) ); + + $position->x += $imageWidth; + } + while ( ( $position->x < $boundings->x1 ) && + ( $repeat & ezcGraph::HORIZONTAL ) ); + } + + /** + * Call all postprocessing functions + * + * @return void + */ + protected function finish() + { + $this->finishCirleSectors(); + $this->finishPieSegmentLabels(); + $this->finishBars(); + $this->finishLineSymbols(); + $this->finishFrontLines(); + + return true; + } + + /** + * Reset renderer properties + * + * Reset all renderer properties, which were calculated during the + * rendering process, to offer a clean environment for rerendering. + * + * @return void + */ + protected function resetRenderer() + { + parent::resetRenderer(); + + // Also reset special 3D renderer options + $this->pieSegmentLabels = array( + 0 => array(), + 1 => array(), + ); + $this->pieSegmentBoundings = false; + $this->linePostSymbols = array(); + $this->frontLines = array(); + $this->circleSectors = array(); + $this->barPostProcessing = array(); + $this->depth = false; + $this->xDepthFactor = false; + $this->yDepthFactor = false; + $this->dataBoundings = false; + $this->axisLabels = array(); + } +} + +?> diff --git a/include/ezcomponents/Graph/src/renderer/axis_label_boxed.php b/include/ezcomponents/Graph/src/renderer/axis_label_boxed.php new file mode 100644 index 000000000..392e85820 --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/axis_label_boxed.php @@ -0,0 +1,227 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisBoxedLabelRenderer(); + * + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisBoxedLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Store step array for later coordinate modifications + * + * @var array(ezcGraphStep) + */ + protected $steps; + + /** + * Store direction for later coordinate modifications + * + * @var ezcGraphVector + */ + protected $direction; + + /** + * Store coordinate width modifier for later coordinate modifications + * + * @var float + */ + protected $widthModifier; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + parent::__construct( $options ); + $this->properties['outerStep'] = true; + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + $this->steps = $steps; + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $this->direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $this->direction->unify(); + + if ( $this->outerGrid ) + { + $gridBoundings = $boundings; + } + else + { + $gridBoundings = new ezcGraphBoundings( + $boundings->x0 + $renderer->xAxisSpace * abs( $this->direction->y ), + $boundings->y0 + $renderer->yAxisSpace * abs( $this->direction->x ), + $boundings->x1 - $renderer->xAxisSpace * abs( $this->direction->y ), + $boundings->y1 - $renderer->yAxisSpace * abs( $this->direction->x ) + ); + } + + // Determine additional required axis space by boxes + $firstStep = reset( $steps ); + $lastStep = end( $steps ); + + $this->widthModifier = 1 + $firstStep->width / 2 + $lastStep->width / 2; + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * ( $step->position + $step->width ) / $this->widthModifier, + $start->y + ( $end->y - $start->y ) * ( $step->position + $step->width ) / $this->widthModifier + ); + + $stepWidth = $step->width / $this->widthModifier; + + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $stepWidth, + $axisBoundings->height * $stepWidth + ); + + if ( $this->showLabels ) + { + // Calculate label boundings + switch ( true ) + { + case ( abs( $this->direction->x ) > abs( $this->direction->y ) ) && + ( $this->direction->x <= 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $stepSize->x + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $renderer->yAxisSpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + break; + case ( abs( $this->direction->x ) > abs( $this->direction->y ) ) && + ( $this->direction->x > 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $stepSize->x - $this->labelPadding, + $position->y + $renderer->yAxisSpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + break; + case ( $this->direction->y <= 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $renderer->xAxisSpace + $this->labelPadding, + $position->y - $stepSize->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + break; + case ( $this->direction->y > 0 ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $renderer->xAxisSpace + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $stepSize->y - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + break; + } + + $renderer->drawText( $labelBoundings, $step->label, $alignement ); + } + + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $this->direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + } + + /** + * Modify chart data position + * + * Optionally additionally modify the coodinate of a data point + * + * @param ezcGraphCoordinate $coordinate Data point coordinate + * @return ezcGraphCoordinate Modified coordinate + */ + public function modifyChartDataPosition( ezcGraphCoordinate $coordinate ) + { + $firstStep = reset( $this->steps ); + $offset = $firstStep->width / 2 / $this->widthModifier; + + return new ezcGraphCoordinate( + $coordinate->x * abs( $this->direction->y ) + + ( $coordinate->x / $this->widthModifier + $offset ) * abs( $this->direction->x ), + $coordinate->y * abs( $this->direction->x ) + + ( $coordinate->y / $this->widthModifier + $offset ) * abs( $this->direction->y ) + ); + } +} +?> diff --git a/include/ezcomponents/Graph/src/renderer/axis_label_centered.php b/include/ezcomponents/Graph/src/renderer/axis_label_centered.php new file mode 100644 index 000000000..ad9809396 --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/axis_label_centered.php @@ -0,0 +1,275 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisCenteredLabelRenderer(); + * + * + * @property bool $showZeroValue + * Show the value at the zero point of an axis. This value might be + * crossed by the other axis which would result in an unreadable + * label. + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisCenteredLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['showZeroValue'] = 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 'showZeroValue': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['showZeroValue'] = (bool) $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + if ( $this->outerGrid ) + { + $gridBoundings = $boundings; + } + else + { + $gridBoundings = new ezcGraphBoundings( + $boundings->x0 + $renderer->xAxisSpace * abs( $direction->y ), + $boundings->y0 + $renderer->yAxisSpace * abs( $direction->x ), + $boundings->x1 - $renderer->xAxisSpace * abs( $direction->y ), + $boundings->y1 - $renderer->yAxisSpace * abs( $direction->x ) + ); + } + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position, + $start->y + ( $end->y - $start->y ) * $step->position + ); + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $step->width, + $axisBoundings->height * $step->width + ); + + if ( ! $step->isZero ) + { + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + + // draw label + if ( $this->showLabels && ( $this->showZeroValue || ! $step->isZero ) ) + { + // Calculate label boundings + if ( abs( $direction->x ) > abs( $direction->y ) ) + { + // Horizontal labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $renderer->xAxisSpace * 2, + $step->width * $axisBoundings->width + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $renderer->xAxisSpace * 2, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->width, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelSize / 2 + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $labelSize / 2 - $this->labelPadding, + $position->y + $renderer->yAxisSpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + } + else + { + // Vertical labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $renderer->yAxisSpace * 2, + $step->width * $axisBoundings->height + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $renderer->yAxisSpace * 2, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->height, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $renderer->xAxisSpace + $this->labelPadding, + $position->y - $labelSize / 2 + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $labelSize / 2 - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + } + + $renderer->drawText( $labelBoundings, $step->label, $alignement ); + } + + // Iterate over minor steps + if ( !$step->isLast ) + { + foreach ( $step->childs as $minorStep ) + { + $minorStepPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $minorStep->position, + $start->y + ( $end->y - $start->y ) * $minorStep->position + ); + $minorStepSize = new ezcGraphCoordinate( + $axisBoundings->width * $minorStep->width, + $axisBoundings->height * $minorStep->width + ); + + if ( $axis->minorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $minorStepPosition, + $minorStepSize, + $axis->minorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $minorStepPosition, + $direction, + $axis->position, + $this->minorStepSize, + $axis->border + ); + } + } + } + } +} +?> diff --git a/include/ezcomponents/Graph/src/renderer/axis_label_exact.php b/include/ezcomponents/Graph/src/renderer/axis_label_exact.php new file mode 100644 index 000000000..9755c24cc --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/axis_label_exact.php @@ -0,0 +1,279 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisExactLabelRenderer(); + * + * + * @property bool $showLastValue + * Show the last value on the axis, which will be aligned different + * than all other values, to not interfere with the arrow head of + * the axis. + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisExactLabelRenderer extends ezcGraphAxisLabelRenderer +{ + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['showLastValue'] = true; + + 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 'showLastValue': + if ( !is_bool( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, 'bool' ); + } + + $this->properties['showLastValue'] = (bool) $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + if ( $this->outerGrid ) + { + $gridBoundings = $boundings; + } + else + { + $gridBoundings = new ezcGraphBoundings( + $boundings->x0 + $renderer->xAxisSpace * abs( $direction->y ), + $boundings->y0 + $renderer->yAxisSpace * abs( $direction->x ), + $boundings->x1 - $renderer->xAxisSpace * abs( $direction->y ), + $boundings->y1 - $renderer->yAxisSpace * abs( $direction->x ) + ); + } + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position, + $start->y + ( $end->y - $start->y ) * $step->position + ); + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $step->width, + $axisBoundings->height * $step->width + ); + + if ( ! $step->isZero ) + { + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + + if ( $this->showLabels ) + { + switch ( $axis->position ) + { + case ezcGraph::RIGHT: + case ezcGraph::LEFT: + $labelWidth = $axisBoundings->width * + $steps[$nr - $step->isLast]->width / + ( $this->showLastValue + 1 ); + $labelHeight = $renderer->yAxisSpace; + break; + + case ezcGraph::BOTTOM: + case ezcGraph::TOP: + $labelWidth = $renderer->xAxisSpace; + $labelHeight = $axisBoundings->height * + $steps[$nr - $step->isLast]->width / + ( $this->showLastValue + 1 ); + break; + } + + $showLabel = true; + switch ( true ) + { + case ( !$this->showLastValue && $step->isLast ): + // Skip last step if showLastValue is false + $showLabel = false; + break; + // Draw label at top left of step + case ( ( $axis->position === ezcGraph::BOTTOM ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::TOP ) && + ( $step->isLast ) ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelWidth + $this->labelPadding, + $position->y - $labelHeight + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y - $this->labelPadding + ); + $alignement = ezcGraph::RIGHT | ezcGraph::BOTTOM; + break; + // Draw label at bottom right of step + case ( ( $axis->position === ezcGraph::LEFT ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::RIGHT ) && + ( $step->isLast ) ): + $labelBoundings = new ezcGraphBoundings( + $position->x + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $labelWidth - $this->labelPadding, + $position->y + $labelHeight - $this->labelPadding + ); + $alignement = ezcGraph::LEFT | ezcGraph::TOP; + break; + // Draw label at bottom left of step + case ( ( $axis->position === ezcGraph::TOP ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::RIGHT ) && + ( !$step->isLast ) ) || + ( ( $axis->position === ezcGraph::BOTTOM ) && + ( $step->isLast ) ) || + ( ( $axis->position === ezcGraph::LEFT ) && + ( $step->isLast ) ): + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelWidth + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $labelHeight - $this->labelPadding + ); + $alignement = ezcGraph::RIGHT | ezcGraph::TOP; + break; + } + + if ( $showLabel ) + { + $renderer->drawText( + $labelBoundings, + $step->label, + $alignement + ); + } + } + + if ( !$step->isLast ) + { + // Iterate over minor steps + foreach ( $step->childs as $minorStep ) + { + $minorStepPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $minorStep->position, + $start->y + ( $end->y - $start->y ) * $minorStep->position + ); + $minorStepSize = new ezcGraphCoordinate( + $axisBoundings->width * $minorStep->width, + $axisBoundings->height * $minorStep->width + ); + + if ( $axis->minorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $minorStepPosition, + $minorStepSize, + $axis->minorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $minorStepPosition, + $direction, + $axis->position, + $this->minorStepSize, + $axis->border + ); + } + } + } + } +} +?> diff --git a/include/ezcomponents/Graph/src/renderer/axis_label_none.php b/include/ezcomponents/Graph/src/renderer/axis_label_none.php new file mode 100644 index 000000000..d1928f651 --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/axis_label_none.php @@ -0,0 +1,44 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisNoLabelRenderer(); + * + * + * @version 1.3 + * @package Graph + */ +class ezcGraphAxisNoLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + return true; + } +} +?> diff --git a/include/ezcomponents/Graph/src/renderer/axis_label_radar.php b/include/ezcomponents/Graph/src/renderer/axis_label_radar.php new file mode 100644 index 000000000..d8720e4d9 --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/axis_label_radar.php @@ -0,0 +1,322 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisRadarLabelRenderer(); + * + * + * @property float $lastStep + * Position of last step on the axis to calculate the grid. + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisRadarLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + $this->properties['lastStep'] = 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 'lastStep': + if ( !is_null( $propertyValue ) && + ( !is_float( $propertyValue ) || + ( $propertyValue < 0 ) || + ( $propertyValue > 1 ) ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float <= 1' ); + } + + $this->properties['lastStep'] = $propertyValue; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $direction = new ezcGraphVector( + $start->x - $end->x, + $start->y - $end->y + ); + $direction->unify(); + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position, + $start->y + ( $end->y - $start->y ) * $step->position + ); + $stepSize = new ezcGraphCoordinate( + $axisBoundings->width * $step->width, + $axisBoundings->height * $step->width + ); + + // Draw major grid + if ( ( $this->lastStep !== null ) && $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $boundings, + $position, + $stepSize, + $axis->majorGrid, + $step->position + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + + // draw label + if ( $this->showLabels && ( $this->lastStep === null ) ) + { + // Calculate label boundings + if ( abs( $direction->x ) > abs( $direction->y ) ) + { + // Horizontal labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $renderer->xAxisSpace * 2, + $step->width * $axisBoundings->width + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $renderer->xAxisSpace * 2, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->width, + $steps[$nr - 1]->width * $axisBoundings->width + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $labelSize / 2 + $this->labelPadding, + $position->y + $this->labelPadding, + $position->x + $labelSize / 2 - $this->labelPadding, + $position->y + $renderer->yAxisSpace - $this->labelPadding + ); + + $alignement = ezcGraph::CENTER | ezcGraph::TOP; + } + else + { + // Vertical labels + switch ( true ) + { + case ( $nr === 0 ): + // First label + $labelSize = min( + $renderer->yAxisSpace * 2, + $step->width * $axisBoundings->height + ); + break; + case ( $step->isLast ): + // Last label + $labelSize = min( + $renderer->yAxisSpace * 2, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + default: + $labelSize = min( + $step->width * $axisBoundings->height, + $steps[$nr - 1]->width * $axisBoundings->height + ); + break; + } + + $labelBoundings = new ezcGraphBoundings( + $position->x - $renderer->xAxisSpace + $this->labelPadding, + $position->y - $labelSize / 2 + $this->labelPadding, + $position->x - $this->labelPadding, + $position->y + $labelSize / 2 - $this->labelPadding + ); + + $alignement = ezcGraph::MIDDLE | ezcGraph::RIGHT; + } + + $renderer->drawText( $labelBoundings, $step->label, $alignement ); + } + + // Iterate over minor steps + if ( !$step->isLast ) + { + foreach ( $step->childs as $minorStep ) + { + $minorStepPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $minorStep->position, + $start->y + ( $end->y - $start->y ) * $minorStep->position + ); + $minorStepSize = new ezcGraphCoordinate( + $axisBoundings->width * $minorStep->width, + $axisBoundings->height * $minorStep->width + ); + + if ( ( $this->lastStep !== null ) && $axis->minorGrid ) + { + $this->drawGrid( + $renderer, + $boundings, + $minorStepPosition, + $minorStepSize, + $axis->minorGrid, + $minorStep->position + ); + } + + // major step + $this->drawStep( + $renderer, + $minorStepPosition, + $direction, + $axis->position, + $this->minorStepSize, + $axis->border + ); + } + } + } + } + + /** + * Draw grid + * + * Draws a grid line at the current position + * + * @param ezcGraphRenderer $renderer Renderer to draw the grid with + * @param ezcGraphBoundings $boundings Boundings of axis + * @param ezcGraphCoordinate $position Position of step + * @param ezcGraphCoordinate $direction Direction of axis + * @param ezcGraphColor $color Color of axis + * @param int $stepPosition + * @return void + */ + protected function drawGrid( ezcGraphRenderer $renderer, ezcGraphBoundings $boundings, ezcGraphCoordinate $position, ezcGraphCoordinate $direction, ezcGraphColor $color, $stepPosition = null ) + { + // Calculate position on last axis + $start = new ezcGraphCoordinate( + $boundings->x0 + $width = ( $boundings->width / 2 ), + $boundings->y0 + $height = ( $boundings->height / 2 ) + ); + + $lastAngle = $this->lastStep * 2 * M_PI; + $end = new ezcGraphCoordinate( + $start->x + sin( $lastAngle ) * $width, + $start->y - cos( $lastAngle ) * $height + ); + + $direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $direction->unify(); + + // Convert elipse to circle for correct angle calculation + $direction->y *= ( $renderer->xAxisSpace / $renderer->yAxisSpace ); + $angle = $direction->angle( new ezcGraphVector( 0, 1 ) ); + + $movement = new ezcGraphVector( + sin( $angle ) * $renderer->xAxisSpace + * ( $direction->x < 0 ? -1 : 1 ), + cos( $angle ) * $renderer->yAxisSpace + ); + + $start->x += $movement->x; + $start->y += $movement->y; + $end->x -= $movement->x; + $end->y -= $movement->y; + + $lastPosition = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $stepPosition, + $start->y + ( $end->y - $start->y ) * $stepPosition + ); + + $renderer->drawGridLine( + $position, + $lastPosition, + $color + ); + } +} +?> diff --git a/include/ezcomponents/Graph/src/renderer/axis_label_rotated.php b/include/ezcomponents/Graph/src/renderer/axis_label_rotated.php new file mode 100644 index 000000000..017e1e2b1 --- /dev/null +++ b/include/ezcomponents/Graph/src/renderer/axis_label_rotated.php @@ -0,0 +1,428 @@ + + * $chart->xAxis->axisLabelRenderer = new ezcGraphAxisRotatedLabelRenderer(); + * + * // Define angle manually in degree + * $chart->xAxis->axisLabelRenderer->angle = 45; + * + * // Increase axis space + * $chart->xAxis->axisSpace = .2; + * + * + * @property float $angle + * Angle of labels on axis in degrees. + * + * @version 1.3 + * @package Graph + * @mainclass + */ +class ezcGraphAxisRotatedLabelRenderer extends ezcGraphAxisLabelRenderer +{ + /** + * Store step array for later coordinate modifications + * + * @var array(ezcGraphStep) + */ + protected $steps; + + /** + * Store direction for later coordinate modifications + * + * @var ezcGraphVector + */ + protected $direction; + + /** + * Store coordinate width modifier for later coordinate modifications + * + * @var float + */ + protected $widthModifier; + + /** + * Store coordinate offset for later coordinate modifications + * + * @var float + */ + protected $offset; + + /** + * Constructor + * + * @param array $options Default option array + * @return void + * @ignore + */ + public function __construct( array $options = array() ) + { + parent::__construct( $options ); + $this->properties['angle'] = null; + } + + /** + * __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 'angle': + if ( !is_numeric( $propertyValue ) ) + { + throw new ezcBaseValueException( $propertyName, $propertyValue, '0 <= float < 360' ); + } + + $reducement = (int) ( $propertyValue - $propertyValue % 360 ); + $this->properties['angle'] = (float) $propertyValue - $reducement; + break; + default: + return parent::__set( $propertyName, $propertyValue ); + } + } + + /** + * Render Axis labels + * + * Render labels for an axis. + * + * @param ezcGraphRenderer $renderer Renderer used to draw the chart + * @param ezcGraphBoundings $boundings Boundings of the axis + * @param ezcGraphCoordinate $start Axis starting point + * @param ezcGraphCoordinate $end Axis ending point + * @param ezcGraphChartElementAxis $axis Axis instance + * @return void + */ + public function renderLabels( + ezcGraphRenderer $renderer, + ezcGraphBoundings $boundings, + ezcGraphCoordinate $start, + ezcGraphCoordinate $end, + ezcGraphChartElementAxis $axis ) + { + // receive rendering parameters from axis + $steps = $axis->getSteps(); + $this->steps = $steps; + + $axisBoundings = new ezcGraphBoundings( + $start->x, $start->y, + $end->x, $end->y + ); + + // Determine normalized axis direction + $this->direction = new ezcGraphVector( + $end->x - $start->x, + $end->y - $start->y + ); + $this->direction->unify(); + $axisAngle = -$this->direction->angle( new ezcGraphVector( 1, 0 ) ); + + if ( $this->outerGrid ) + { + $gridBoundings = $boundings; + } + else + { + $gridBoundings = new ezcGraphBoundings( + $boundings->x0 + $renderer->xAxisSpace, + $boundings->y0 + $renderer->yAxisSpace, + $boundings->x1 - $renderer->xAxisSpace, + $boundings->y1 - $renderer->yAxisSpace + ); + } + + // Determine optimal angle if none specified + if ( $this->angle === null ) + { + $minimumStepWidth = null; + foreach ( $steps as $nr => $step ) + { + if ( ( $minimumStepWidth === null ) || + ( $step->width < $minimumStepWidth ) ) + { + $minimumStepWidth = $step->width; + } + } + + $width = abs( + $axisBoundings->width * $minimumStepWidth * $this->direction->x + + $axisBoundings->height * $minimumStepWidth * $this->direction->y + ); + $height = abs( + $renderer->yAxisSpace * $this->direction->x + + $renderer->xAxisSpace * $this->direction->y + ); + + $length = sqrt( pow( $width, 2 ) + pow( $height, 2 ) ); + $this->angle = rad2deg( acos( $height / $length ) ); + } + + // Determine additional required axis space by boxes + $firstStep = reset( $steps ); + $lastStep = end( $steps ); + + $textAngle = $axisAngle + + deg2rad( $this->angle ) + + ( $axis->position & ( ezcGraph::TOP | ezcGraph::BOTTOM ) ? deg2rad( 270 ) : deg2rad( 90 ) ); + + // Ensure angle between 0 and 360 degrees + $degTextAngle = rad2deg( $textAngle ); + while ( $degTextAngle < 0 ) + { + $degTextAngle += 360.; + } + + $this->offset = + ( $this->angle < 0 ? -1 : 1 ) * + ( $axis->position & ( ezcGraph::TOP | ezcGraph::LEFT ) ? 1 : -1 ) * + ( 1 - cos( deg2rad( $this->angle * 2 ) ) ); + + $axisSpaceFactor = abs( + ( $this->direction->x == 0 ? 0 : + $this->direction->x * $renderer->yAxisSpace / $axisBoundings->width ) + + ( $this->direction->y == 0 ? 0 : + $this->direction->y * $renderer->xAxisSpace / $axisBoundings->height ) + ); + + $start = new ezcGraphCoordinate( + $start->x + max( 0., $axisSpaceFactor * $this->offset ) * ( $end->x - $start->x ), + $start->y + max( 0., $axisSpaceFactor * $this->offset ) * ( $end->y - $start->y ) + ); + $end = new ezcGraphCoordinate( + $end->x + min( 0., $axisSpaceFactor * $this->offset ) * ( $end->x - $start->x ), + $end->y + min( 0., $axisSpaceFactor * $this->offset ) * ( $end->y - $start->y ) + ); + + $labelLength = sqrt( + pow( + $renderer->xAxisSpace * $this->direction->y + + $axisSpaceFactor * $this->offset * ( $end->x - $start->x ), + 2 ) + + pow( + $renderer->yAxisSpace * $this->direction->x + + $axisSpaceFactor * $this->offset * ( $end->y - $start->y ), + 2 ) + ); + + $this->offset *= $axisSpaceFactor; + + // Draw steps and grid + foreach ( $steps as $nr => $step ) + { + $position = new ezcGraphCoordinate( + $start->x + ( $end->x - $start->x ) * $step->position * abs( $this->direction->x ), + $start->y + ( $end->y - $start->y ) * $step->position * abs( $this->direction->y ) + ); + + $stepSize = new ezcGraphCoordinate( + ( $end->x - $start->x ) * $step->width, + ( $end->y - $start->y ) * $step->width + ); + + // Calculate label boundings + switch ( true ) + { + case ( $nr === 0 ): + $labelSize = min( + abs( + $renderer->xAxisSpace * 2 * $this->direction->x + + $renderer->yAxisSpace * 2 * $this->direction->y ), + abs( + $step->width * $axisBoundings->width * $this->direction->x + + $step->width * $axisBoundings->height * $this->direction->y ) + ); + break; + case ( $step->isLast ): + $labelSize = min( + abs( + $renderer->xAxisSpace * 2 * $this->direction->x + + $renderer->yAxisSpace * 2 * $this->direction->y ), + abs( + $steps[$nr - 1]->width * $axisBoundings->width * $this->direction->x + + $steps[$nr - 1]->width * $axisBoundings->height * $this->direction->y ) + ); + break; + default: + $labelSize = min( + abs( + $step->width * $axisBoundings->width * $this->direction->x + + $step->width * $axisBoundings->height * $this->direction->y ), + abs( + $steps[$nr - 1]->width * $axisBoundings->width * $this->direction->x + + $steps[$nr - 1]->width * $axisBoundings->height * $this->direction->y ) + ); + break; + } + + $labelSize = $labelSize * cos( deg2rad( $this->angle ) ); + $lengthReducement = min( + abs( tan( deg2rad( $this->angle ) ) * ( $labelSize / 2 ) ), + abs( $labelLength / 2 ) + ); + + switch ( true ) + { + case ( ( ( $degTextAngle >= 0 ) && + ( $degTextAngle < 90 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 270 ) && + ( $degTextAngle < 360 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x, + $position->y, + $position->x + abs( $labelLength ) - $lengthReducement, + $position->y + $labelSize + ); + $labelAlignement = ezcGraph::LEFT | ezcGraph::TOP; + $labelRotation = $degTextAngle; + break; + case ( ( ( $degTextAngle >= 90 ) && + ( $degTextAngle < 180 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 180 ) && + ( $degTextAngle < 270 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x - abs( $labelLength ) + $lengthReducement, + $position->y, + $position->x, + $position->y + $labelSize + ); + $labelAlignement = ezcGraph::RIGHT | ezcGraph::TOP; + $labelRotation = $degTextAngle - 180; + break; + case ( ( ( $degTextAngle >= 180 ) && + ( $degTextAngle < 270 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 90 ) && + ( $degTextAngle < 180 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x - abs( $labelLength ) + $lengthReducement, + $position->y - $labelSize, + $position->x, + $position->y + ); + $labelAlignement = ezcGraph::RIGHT | ezcGraph::BOTTOM; + $labelRotation = $degTextAngle - 180; + break; + case ( ( ( $degTextAngle >= 270 ) && + ( $degTextAngle < 360 ) && + ( ( $axis->position === ezcGraph::LEFT ) || + ( $axis->position === ezcGraph::RIGHT ) + ) + ) || + ( ( $degTextAngle >= 0 ) && + ( $degTextAngle < 90 ) && + ( ( $axis->position === ezcGraph::TOP ) || + ( $axis->position === ezcGraph::BOTTOM ) + ) + ) + ): + $labelBoundings = new ezcGraphBoundings( + $position->x, + $position->y + $labelSize, + $position->x + abs( $labelLength ) - $lengthReducement, + $position->y + ); + $labelAlignement = ezcGraph::LEFT | ezcGraph::BOTTOM; + $labelRotation = $degTextAngle; + break; + } + + $renderer->drawText( + $labelBoundings, + $step->label, + $labelAlignement, + new ezcGraphRotation( + $labelRotation, + $position + ) + ); + + // major grid + if ( $axis->majorGrid ) + { + $this->drawGrid( + $renderer, + $gridBoundings, + $position, + $stepSize, + $axis->majorGrid + ); + } + + // major step + $this->drawStep( + $renderer, + $position, + $this->direction, + $axis->position, + $this->majorStepSize, + $axis->border + ); + } + } + + /** + * Modify chart data position + * + * Optionally additionally modify the coodinate of a data point + * + * @param ezcGraphCoordinate $coordinate Data point coordinate + * @return ezcGraphCoordinate Modified coordinate + */ + public function modifyChartDataPosition( ezcGraphCoordinate $coordinate ) + { + return new ezcGraphCoordinate( + $coordinate->x * abs( $this->direction->y ) + + ( $coordinate->x * ( 1 - abs( $this->offset ) ) + max( 0, $this->offset ) ) * abs( $this->direction->x ), + $coordinate->y * abs( $this->direction->x ) + + ( $coordinate->y * ( 1 - abs( $this->offset ) ) + max( 0, $this->offset ) ) * abs( $this->direction->y ) + ); + } +} +?> diff --git a/include/ezcomponents/Graph/src/structs/context.php b/include/ezcomponents/Graph/src/structs/context.php new file mode 100644 index 000000000..d3fae208c --- /dev/null +++ b/include/ezcomponents/Graph/src/structs/context.php @@ -0,0 +1,76 @@ +dataset = $dataset; + $this->datapoint = $datapoint; + $this->url = $url; + } + + /** + * __set_state + * + * @param array $properties Struct properties + * @return void + * @ignore + */ + public function __set_state( array $properties ) + { + $this->dataset = (string) $properties['dataset']; + $this->datapoint = (string) $properties['datapoint']; + + // Check to keep BC + // @TODO: Remvove unnesecary check on next major version + if ( array_key_exists( 'url', $properties ) ) + { + $this->url = (string) $properties['url']; + } + } +} + +?> diff --git a/include/ezcomponents/Graph/src/structs/coordinate.php b/include/ezcomponents/Graph/src/structs/coordinate.php new file mode 100644 index 000000000..ac1d5b9b3 --- /dev/null +++ b/include/ezcomponents/Graph/src/structs/coordinate.php @@ -0,0 +1,70 @@ +x = $x; + $this->y = $y; + } + + /** + * __set_state + * + * @param array $properties Struct properties + * @return void + * @ignore + */ + public function __set_state( array $properties ) + { + $this->x = $properties['x']; + $this->y = $properties['y']; + } + + /** + * Returns simple string representation of coordinate + * + * @return string + * @ignore + */ + public function __toString() + { + return sprintf( '( %.2f, %.2f )', $this->x, $this->y ); + } +} + +?> diff --git a/include/ezcomponents/Graph/src/structs/step.php b/include/ezcomponents/Graph/src/structs/step.php new file mode 100644 index 000000000..7d17845a4 --- /dev/null +++ b/include/ezcomponents/Graph/src/structs/step.php @@ -0,0 +1,99 @@ +position = (float) $position; + $this->width = (float) $width; + $this->label = $label; + $this->childs = $childs; + $this->isZero = (bool) $isZero; + $this->isLast = (bool) $isLast; + } + + /** + * __set_state + * + * @param array $properties Struct properties + * @return void + * @ignore + */ + public function __set_state( array $properties ) + { + $this->position = $properties['position']; + $this->width = $properties['width']; + $this->label = $properties['label']; + $this->childs = $properties['childs']; + $this->isZero = $properties['isZero']; + $this->isLast = $properties['isLast']; + } +} + +?> diff --git a/include/ezcomponents/Graph/src/tools.php b/include/ezcomponents/Graph/src/tools.php new file mode 100644 index 000000000..5bc403c0c --- /dev/null +++ b/include/ezcomponents/Graph/src/tools.php @@ -0,0 +1,183 @@ +driver instanceof ezcGraphGdDriver ) ) + { + throw new ezcGraphToolsIncompatibleDriverException( $chart->driver, 'ezcGraphGdDriver' ); + } + + $elements = $chart->renderer->getElementReferences(); + + if ( !count( $elements ) ) + { + throw new ezcGraphToolsNotRenderedException( $chart ); + } + + $imageMap = sprintf( "\n", $name ); + + // Iterate over legends elements + if ( isset( $elements['legend'] ) ) + { + foreach ( $elements['legend'] as $objectName => $polygones ) + { + $url = $elements['legend_url'][$objectName]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $polygones as $shape => $polygone ) + { + $coordinateString = ''; + foreach ( $polygone as $coordinate ) + { + $coordinateString .= sprintf( '%d,%d,', $coordinate->x, $coordinate->y ); + } + + $imageMap .= sprintf( "\t\"%s\"\n", + substr( $coordinateString, 0, -1 ), + $url, + $objectName + ); + } + } + } + + // Iterate over data + foreach ( $elements['data'] as $dataset => $datapoints ) + { + foreach ( $datapoints as $datapoint => $polygones ) + { + $url = $chart->data[$dataset]->url[$datapoint]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $polygones as $polygon ) + { + $coordinateString = ''; + foreach ( $polygon as $coordinate ) + { + $coordinateString .= sprintf( '%d,%d,', $coordinate->x, $coordinate->y ); + } + + $imageMap .= sprintf( "\t\"%s\"\n", + substr( $coordinateString, 0, -1 ), + $url, + $datapoint + ); + } + } + } + + return $imageMap . "\n"; + } + + /** + * Add links to clickable SVG elements in a chart with SVG driver. + * + * @param ezcGraphChart $chart + * @return void + */ + public static function linkSvgElements( ezcGraphChart $chart ) + { + if ( ! ( $chart->driver instanceof ezcGraphSvgDriver ) ) + { + throw new ezcGraphToolsIncompatibleDriverException( $chart->driver, 'ezcGraphSvgDriver' ); + } + + $fileName = $chart->getRenderedFile(); + + if ( !$fileName ) + { + throw new ezcGraphToolsNotRenderedException( $chart ); + } + + $dom = new DOMDocument(); + $dom->load( $fileName ); + $xpath = new DomXPath( $dom ); + + $elements = $chart->renderer->getElementReferences(); + + // Link chart elements + foreach ( $elements['data'] as $dataset => $datapoints ) + { + foreach ( $datapoints as $datapoint => $ids ) + { + $url = $chart->data[$dataset]->url[$datapoint]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $ids as $id ) + { + $element = $xpath->query( '//*[@id = \'' . $id . '\']' )->item( 0 ); + + $element->setAttribute( 'style', $element->getAttribute( 'style' ) . ' cursor: ' . $chart->driver->options->linkCursor . ';' ); + $element->setAttribute( 'onclick', "top.location = '{$url}'" ); + } + } + } + + // Link legend elements + if ( isset( $elements['legend'] ) ) + { + foreach ( $elements['legend'] as $objectName => $ids ) + { + $url = $elements['legend_url'][$objectName]; + + if ( empty( $url ) ) + { + continue; + } + + foreach ( $ids as $id ) + { + $element = $xpath->query( '//*[@id = \'' . $id . '\']' )->item( 0 ); + + $element->setAttribute( 'style', $element->getAttribute( 'style' ) . ' cursor: ' . $chart->driver->options->linkCursor . ';' ); + $element->setAttribute( 'onclick', "top.location = '{$url}'" ); + } + } + } + + $dom->save( $fileName ); + } +} + +?> diff --git a/include/ezcomponents/GraphDatabaseTiein/CREDITS b/include/ezcomponents/GraphDatabaseTiein/CREDITS new file mode 100644 index 000000000..2cc9fc273 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/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/GraphDatabaseTiein/ChangeLog b/include/ezcomponents/GraphDatabaseTiein/ChangeLog new file mode 100644 index 000000000..402744c3f --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/ChangeLog @@ -0,0 +1,17 @@ +1.0 - Monday 02 July 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes. + + +1.0rc1 - Monday 25 June 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Documentation updates and fixes. + + +1.0beta1 - Monday 07 May 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Implemented feature #9405: Allow creation of datasets directly from PDO + statements. diff --git a/include/ezcomponents/GraphDatabaseTiein/DEPS b/include/ezcomponents/GraphDatabaseTiein/DEPS new file mode 100644 index 000000000..759b60d1c --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/DEPS @@ -0,0 +1 @@ +Graph: 1.1 diff --git a/include/ezcomponents/GraphDatabaseTiein/DESCRIPTION b/include/ezcomponents/GraphDatabaseTiein/DESCRIPTION new file mode 100644 index 000000000..0a1e4009c --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/DESCRIPTION @@ -0,0 +1,2 @@ +The GraphDatabaseTiein provides functionality to directly use PDO statements +as basis for ezcGraph Datasets. diff --git a/include/ezcomponents/GraphDatabaseTiein/TODO b/include/ezcomponents/GraphDatabaseTiein/TODO new file mode 100644 index 000000000..d82dbbb34 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/TODO @@ -0,0 +1,3 @@ +TODO +==== + diff --git a/include/ezcomponents/GraphDatabaseTiein/design/class_diagram.png b/include/ezcomponents/GraphDatabaseTiein/design/class_diagram.png new file mode 100644 index 000000000..4ac03cdf8 Binary files /dev/null and b/include/ezcomponents/GraphDatabaseTiein/design/class_diagram.png differ diff --git a/include/ezcomponents/GraphDatabaseTiein/design/design.txt b/include/ezcomponents/GraphDatabaseTiein/design/design.txt new file mode 100644 index 000000000..7de86d76f --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/design/design.txt @@ -0,0 +1,29 @@ +eZ publish Enterprise Component: GraphDatabaseTiein, Design +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +:Author: Kore Nordmann +:Revision: $id$ +:Date: $Date: 2007-05-03 08:50:00 +0200 (Thu, 03 May 2007) $ + +Design Description +================== + +The GraphDatabaseTiein provides functionality to directly use PDO statements +as basis for Datasets. You can either use result sets with one or two columns +directly or specify in the data set constructor which columns to use as data +set keys and values. + +Main Classes +============ + +- ezcGraphPdoDataSet extends ezcGraphDataSet + + This class receives a PDO statement and can be used as a data set for all + chart types. + + +.. + Local Variables: + mode: rst + fill-column: 79 + End: + vim: et syn=rst tw=79 diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_multiple.svg.png b/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_multiple.svg.png new file mode 100644 index 000000000..603a95826 Binary files /dev/null and b/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_multiple.svg.png differ diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_simple.svg.png b/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_simple.svg.png new file mode 100644 index 000000000..db8d6e5b0 Binary files /dev/null and b/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_simple.svg.png differ diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_single.svg.png b/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_single.svg.png new file mode 100644 index 000000000..5dd21d968 Binary files /dev/null and b/include/ezcomponents/GraphDatabaseTiein/docs/img/tutorial_single.svg.png differ diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/tutorial.txt b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial.txt new file mode 100644 index 000000000..70eae7d3d --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial.txt @@ -0,0 +1,132 @@ +================================== +eZ Components - GraphDatabaseTieIn +================================== + +.. contents:: Table of Contents + :depth: 2 + +Introduction +============ + +The GraphDatabaseTieIn allows you to directly display results from a database +query in a graph using ezcGraph. All data represented in PDOStatements, for +example returned by the database component, can be displayed. + +Class overview +============== + +This section gives you an overview of the most important classes. + +ezcGraphDatabaseDataSet + Extends the ezcGraphDataset to read data from a PDOStatement and prepare it + for the usage as a dataset in the graph component. + +Setup +===== + +For the examples we expect a simple database setup, so that we are able to +test our examples. For this we create a table with some statistical data to be +used in the graphs. + +.. include:: tutorial/tutorial_insert_data.php + :literal: + +We first include the common autoload file to set up the autoload for +ezcComponents. Then, in line 5, we connect to a SQLite memory database, we +later fill up with some data representing a random browser statistic. The +usage idescription of PDO__ and the `database component`__ can be found at +the dedicated documentation pages. + +__ http://php.net/PDO +__ introduction_Database.html + +Usage +===== + +Default behaviour +----------------- + +To create a simple pie chart we just select the data and add a new dataset, +created from the resulting statement, to a new chart. + +.. include:: tutorial/tutorial_simple.php + :literal: + +The query builder we use to create the select query in line 8 to 10 is +described in detail in the `database components documentation`__. In this +example all values from the columns hits and browser are selected from the +table browser_hits. The result of the query is available in $statement after +the query was executed. The $statement object is an instance of the +PDOStatement class. + +__ introduction_Database.html + +The creation of charts is described in detail in the `graph components +documentation`__. In this example we create a simple pie chart, set a title +for the chart and add a new dataset. To directly use a PDOStatement as a data +source an instance of ezcGraphDatabaseDataSet is created with the $statement +as the first parameter. By default the first column is used as index and the +second column as values for the indices. The result is the pie chart we +expected. + +__ introduction_Graph.html + +.. image:: img/tutorial_simple.svg.png + :alt: Simple pie chart example + +Single column +------------- + +.. include:: tutorial/tutorial_simple.php + :literal: + +When only a single column is returned by the select query the values are +considered as a zero indexed array. This might be useful to display them in +line or bar charts. + +The created data set may be used in the same way like all other data sets, +which can be seen in line 20 in the example above, where a average polynomial +data set is created from the database data set. More documentation on average +datasets can be found in the `graph tutorial`__. + +__ introduction_Graph.html#average-polynomial-dataset + +.. image:: img/tutorial_single.svg.png + :alt: Line chart example + +Multiple columns +---------------- + +You also may specify which column should be used as a key and which column +should be used as a value in the created dataset. This is particulary useful +when dealing with more then two columns. + +.. include:: tutorial/tutorial_multiple.php + :literal: + +In this example all columns from the table are selected using the \*,but the +array starting in line 21 defines which columns are used for keys and values. +There are two array keys, which are constants defined in ezcGraph, referencing +the name of the column to use. + +Starting at line 28 we change the renderer and enhance the output a bit. This +is described in more detail in the `3D renderer section`__ in the graph +tutorial. + +__ introduction_Graph.html#id2 + +.. image:: img/tutorial_multiple.svg.png + :alt: Pie chart from multiple columns + +More information +================ + +For more information, see the ezcGraphDatabaseTieIn API documentation. + + +.. + Local Variables: + mode: rst + fill-column: 79 + End: + vim: et syn=rst tw=79 diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_autoload.php b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_autoload.php new file mode 100644 index 000000000..8b197deaa --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_autoload.php @@ -0,0 +1,20 @@ + diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_insert_data.php b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_insert_data.php new file mode 100644 index 000000000..9a65bf164 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_insert_data.php @@ -0,0 +1,19 @@ +exec( 'CREATE TABLE browser_hits ( id INT, browser VARCHAR(255), hits INT )' ); + +// Insert some data +$db->exec( "INSERT INTO browser_hits VALUES ( NULL, 'Firefox', 2567 )" ); +$db->exec( "INSERT INTO browser_hits VALUES ( NULL, 'Opera', 543 )" ); +$db->exec( "INSERT INTO browser_hits VALUES ( NULL, 'Safari', 23 )" ); +$db->exec( "INSERT INTO browser_hits VALUES ( NULL, 'Konquror', 812 )" ); +$db->exec( "INSERT INTO browser_hits VALUES ( NULL, 'Lynx', 431 )" ); +$db->exec( "INSERT INTO browser_hits VALUES ( NULL, 'wget', 912 )" ); + +?> diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_multiple.php b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_multiple.php new file mode 100644 index 000000000..5e9cd95d2 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_multiple.php @@ -0,0 +1,45 @@ +createSelectQuery(); +$query + ->select( '*' ) + ->from( 'browser_hits' ); +$statement = $query->prepare(); +$statement->execute(); + +// Create chart from data +$chart = new ezcGraphPieChart(); +$chart->title = 'Browser statistics'; +$chart->legend = false; + +$chart->data['browsers'] = new ezcGraphDatabaseDataSet( + $statement, + array( + ezcGraph::KEY => 'browser', + ezcGraph::VALUE => 'hits', + ) +); + +// Some graph output formatting +$chart->renderer = new ezcGraphRenderer3d(); + +$chart->renderer->options->pieChartGleam = .3; +$chart->renderer->options->pieChartGleamColor = '#FFFFFF'; +$chart->renderer->options->dataBorder = false; + +$chart->renderer->options->pieChartShadowSize = 5; +$chart->renderer->options->pieChartShadowColor = '#000000'; + +$chart->renderer->options->pieChartSymbolColor = '#55575388'; + +$chart->renderer->options->pieChartHeight = 5; +$chart->renderer->options->pieChartRotation = .8; + +// Render +$chart->render( 400, 150, 'tutorial_multiple.svg' ); + +?> diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_simple.php b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_simple.php new file mode 100644 index 000000000..c735d3edf --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_simple.php @@ -0,0 +1,22 @@ +createSelectQuery(); +$query + ->select( 'browser', 'hits' ) + ->from( 'browser_hits' ); +$statement = $query->prepare(); +$statement->execute(); + +// Create chart from data +$chart = new ezcGraphPieChart(); +$chart->title = 'Browser statistics'; + +$chart->data['browsers'] = new ezcGraphDatabaseDataSet( $statement ); + +$chart->render( 400, 200, 'tutorial_simple.svg' ); + +?> diff --git a/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_single.php b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_single.php new file mode 100644 index 000000000..4d00ddc54 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/docs/tutorial/tutorial_single.php @@ -0,0 +1,26 @@ +createSelectQuery(); +$query + ->select( 'hits' ) + ->from( 'browser_hits' ); +$statement = $query->prepare(); +$statement->execute(); + +// Create chart from data +$chart = new ezcGraphLineChart(); +$chart->title = 'Browser statistics'; +$chart->options->fillLines = 220; + +$chart->data['browsers'] = new ezcGraphDatabaseDataSet( $statement ); +$chart->data['average'] = new ezcGraphDataSetAveragePolynom( + $chart->data['browsers'] +); + +$chart->render( 400, 150, 'tutorial_single.svg' ); + +?> diff --git a/include/ezcomponents/GraphDatabaseTiein/src/dataset.php b/include/ezcomponents/GraphDatabaseTiein/src/dataset.php new file mode 100644 index 000000000..bb3914a6b --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/src/dataset.php @@ -0,0 +1,139 @@ + 'row name', + * ezcGraph::VALUE => 'row name', + * ); + * + * @param PDOStatement $statement + * @param array $definition + * @return ezcGraphDatabase + */ + public function __construct( PDOStatement $statement, array $definition = null ) + { + parent::__construct(); + + $this->data = array(); + $this->createFromPdo( $statement, $definition ); + } + + /** + * Create dataset from PDO statement + * + * This methods uses the values from a PDOStatement to fill up the data + * sets data. + * + * If the definition array is empty a single column will be used as values, + * with two columns the first column will be used for the keys and the + * second for the data set values. + * + * You may define the name of the rows used for keys and values by using + * an array like: + * array ( + * ezcGraph::KEY => 'row name', + * ezcGraph::VALUE => 'row name', + * ); + * + * @param PDOStatement $statement + * @param array $definition + * @return void + */ + protected function createFromPdo( PDOStatement $statement, array $definition = null ) + { + $count = 0; + + if ( $definition === null ) + { + while ( $row = $statement->fetch( PDO::FETCH_NUM ) ) + { + ++$count; + + switch ( count( $row ) ) + { + case 1: + $this->data[] = $row[0]; + break; + case 2: + $this->data[$row[0]] = $row[1]; + break; + default: + throw new ezcGraphDatabaseTooManyColumnsException( $row ); + } + } + } + else + { + while ( $row = $statement->fetch( PDO::FETCH_NAMED ) ) + { + ++$count; + + if ( !array_key_exists( $definition[ezcGraph::VALUE], $row ) ) + { + throw new ezcGraphDatabaseMissingColumnException( $definition[ezcGraph::VALUE] ); + } + + $value = $row[$definition[ezcGraph::VALUE]]; + + if ( array_key_exists( ezcGraph::KEY, $definition ) ) + { + if ( !array_key_exists( $definition[ezcGraph::KEY], $row ) ) + { + throw new ezcGraphDatabaseMissingColumnException( $definition[ezcGraph::KEY] ); + } + + $this->data[$row[$definition[ezcGraph::KEY]]] = $value; + } + else + { + $this->data[] = $value; + } + } + } + + // Empty result set + if ( $count <= 0 ) + { + throw new ezcGraphDatabaseStatementNotExecutedException( $statement ); + } + } + + /** + * Returns the number of elements in this dataset + * + * @return int + */ + public function count() + { + return count( $this->data ); + } +} + +?> diff --git a/include/ezcomponents/GraphDatabaseTiein/src/exceptions/exception.php b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/exception.php new file mode 100644 index 000000000..307f0d8a1 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/exception.php @@ -0,0 +1,20 @@ + diff --git a/include/ezcomponents/GraphDatabaseTiein/src/exceptions/missing_column.php b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/missing_column.php new file mode 100644 index 000000000..f22153870 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/missing_column.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/GraphDatabaseTiein/src/exceptions/statement_not_executed.php b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/statement_not_executed.php new file mode 100644 index 000000000..0ce708952 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/statement_not_executed.php @@ -0,0 +1,31 @@ + diff --git a/include/ezcomponents/GraphDatabaseTiein/src/exceptions/too_many_columns.php b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/too_many_columns.php new file mode 100644 index 000000000..216e23141 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/src/exceptions/too_many_columns.php @@ -0,0 +1,33 @@ + diff --git a/include/ezcomponents/GraphDatabaseTiein/tests/dataset_pdo_test.php b/include/ezcomponents/GraphDatabaseTiein/tests/dataset_pdo_test.php new file mode 100644 index 000000000..04dd74ca6 --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/tests/dataset_pdo_test.php @@ -0,0 +1,374 @@ +tempDir = $this->createTempDir( __CLASS__ . sprintf( '_%03d_', ++$i ) ) . '/'; + $this->basePath = dirname( __FILE__ ) . '/data/'; + + // Try to build up database connection + try + { + $db = ezcDbInstance::get(); + } + catch ( Exception $e ) + { + $this->markTestSkipped( 'Database connection required for PDO statement tests.' ); + } + + $this->q = new ezcQueryInsert( $db ); + try + { + $db->exec( 'DROP TABLE graph_pdo_test' ); + } + catch ( Exception $e ) {} // eat + + // Create test table + $db->exec( 'CREATE TABLE graph_pdo_test ( id INT, browser VARCHAR(255), hits INT )' ); + + // Insert some data + $db->exec( "INSERT INTO graph_pdo_test VALUES ( '', 'Firefox', 2567 )" ); + $db->exec( "INSERT INTO graph_pdo_test VALUES ( '', 'Opera', 543 )" ); + $db->exec( "INSERT INTO graph_pdo_test VALUES ( '', 'Safari', 23 )" ); + $db->exec( "INSERT INTO graph_pdo_test VALUES ( '', 'Konquror', 812 )" ); + $db->exec( "INSERT INTO graph_pdo_test VALUES ( '', 'Lynx', 431 )" ); + $db->exec( "INSERT INTO graph_pdo_test VALUES ( '', 'wget', 912 )" ); + } + + protected function tearDown() + { + if ( !$this->hasFailed() ) + { + $this->removeTempDir(); + } + + $db = ezcDbInstance::get(); + $db->exec( 'DROP TABLE graph_pdo_test' ); + } + + public function testAutomaticDataSetUsage() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT browser, hits FROM graph_pdo_test' ); + $statement->execute(); + + $dataset = new ezcGraphDatabaseDataSet( $statement ); + + $dataSetArray = array( + 'Firefox' => 2567, + 'Opera' => 543, + 'Safari' => 23, + 'Konquror' => 812, + 'Lynx' => 431, + 'wget' => 912, + ); + + $count = 0; + foreach ( $dataset as $key => $value ) + { + list( $compareKey, $compareValue ) = each( $dataSetArray ); + + $this->assertEquals( + $compareKey, + $key, + 'Unexpected key for dataset value.' + ); + + $this->assertEquals( + $compareValue, + $value, + 'Unexpected value for dataset.' + ); + + ++$count; + } + + $this->assertEquals( + $count, + count( $dataSetArray ), + 'Too few datasets found.' + ); + } + + public function testAutomaticDataSetUsageSingleColumn() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT hits FROM graph_pdo_test' ); + $statement->execute(); + + $dataset = new ezcGraphDatabaseDataSet( $statement ); + + $dataSetArray = array( + 'Firefox' => 2567, + 'Opera' => 543, + 'Safari' => 23, + 'Konquror' => 812, + 'Lynx' => 431, + 'wget' => 912, + ); + + $count = 0; + foreach ( $dataset as $key => $value ) + { + list( $compareKey, $compareValue ) = each( $dataSetArray ); + + $this->assertEquals( + $count, + $key, + 'Unexpected key for dataset value.' + ); + + $this->assertEquals( + $compareValue, + $value, + 'Unexpected value for dataset.' + ); + + ++$count; + } + + $this->assertEquals( + $count, + count( $dataSetArray ), + 'Too few datasets found.' + ); + } + + public function testAutomaticDataSetUsageTooManyRows() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT * FROM graph_pdo_test' ); + $statement->execute(); + + try + { + $dataset = new ezcGraphDatabaseDataSet( $statement ); + } + catch ( ezcGraphDatabaseTooManyColumnsException $e ) + { + return true; + } + + $this->fail( 'Expected ezcGraphDatabaseTooManyColumnsException.' ); + } + + public function testSpecifiedDataSetUsage() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT * FROM graph_pdo_test' ); + $statement->execute(); + + $dataset = new ezcGraphDatabaseDataSet( + $statement, + array( + ezcGraph::KEY => 'browser', + ezcGraph::VALUE => 'hits', + ) + ); + + $dataSetArray = array( + 'Firefox' => 2567, + 'Opera' => 543, + 'Safari' => 23, + 'Konquror' => 812, + 'Lynx' => 431, + 'wget' => 912, + ); + + $count = 0; + foreach ( $dataset as $key => $value ) + { + list( $compareKey, $compareValue ) = each( $dataSetArray ); + + $this->assertEquals( + $compareKey, + $key, + 'Unexpected key for dataset value.' + ); + + $this->assertEquals( + $compareValue, + $value, + 'Unexpected value for dataset.' + ); + + ++$count; + } + + $this->assertEquals( + $count, + count( $dataSetArray ), + 'Too few datasets found.' + ); + } + + public function testSpecifiedDataSetUsageSingleColumn() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT * FROM graph_pdo_test' ); + $statement->execute(); + + $dataset = new ezcGraphDatabaseDataSet( + $statement, + array( + ezcGraph::VALUE => 'hits', + ) + ); + + $dataSetArray = array( + 'Firefox' => 2567, + 'Opera' => 543, + 'Safari' => 23, + 'Konquror' => 812, + 'Lynx' => 431, + 'wget' => 912, + ); + + $count = 0; + foreach ( $dataset as $key => $value ) + { + list( $compareKey, $compareValue ) = each( $dataSetArray ); + + $this->assertEquals( + $count, + $key, + 'Unexpected key for dataset value.' + ); + + $this->assertEquals( + $compareValue, + $value, + 'Unexpected value for dataset.' + ); + + ++$count; + } + + $this->assertEquals( + $count, + count( $dataSetArray ), + 'Too few datasets found.' + ); + } + + public function testSpecifiedDataSetUsageBrokenKey() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT * FROM graph_pdo_test' ); + $statement->execute(); + + try + { + $dataset = new ezcGraphDatabaseDataSet( + $statement, + array( + ezcGraph::KEY => 'nonexistant', + ezcGraph::VALUE => 'hits', + ) + ); + } + catch ( ezcGraphDatabaseMissingColumnException $e ) + { + return true; + } + + $this->fail( 'Expected ezcGraphDatabaseMissingColumnException.' ); + } + + public function testSpecifiedDataSetUsageBrokenValue() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT * FROM graph_pdo_test' ); + $statement->execute(); + + try + { + $dataset = new ezcGraphDatabaseDataSet( + $statement, + array( + ezcGraph::VALUE => 'nonexistant', + ) + ); + } + catch ( ezcGraphDatabaseMissingColumnException $e ) + { + return true; + } + + $this->fail( 'Expected ezcGraphDatabaseMissingColumnException.' ); + } + + public function testNonExceutedQuery() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT browser, hits FROM graph_pdo_test' ); + + try + { + $dataset = new ezcGraphDatabaseDataSet( $statement ); + } + catch ( ezcGraphDatabaseStatementNotExecutedException $e ) + { + return true; + } + + $this->fail( 'Expected ezcGraphDatabaseStatementNotExecutedException.' ); + } + + public function testDataSetCount() + { + $db = ezcDbInstance::get(); + + $statement = $db->prepare( 'SELECT * FROM graph_pdo_test' ); + $statement->execute(); + + $dataset = new ezcGraphDatabaseDataSet( + $statement, + array( + ezcGraph::VALUE => 'hits', + ) + ); + + $this->assertEquals( + count( $dataset ), + 6, + 'Wrong data set item count returned' + ); + } +} + +?> diff --git a/include/ezcomponents/GraphDatabaseTiein/tests/suite.php b/include/ezcomponents/GraphDatabaseTiein/tests/suite.php new file mode 100644 index 000000000..89727f66c --- /dev/null +++ b/include/ezcomponents/GraphDatabaseTiein/tests/suite.php @@ -0,0 +1,31 @@ +setName( 'GraphDatabaseTiein' ); + + $this->addTest( ezcGraphDatabaseTest::suite() ); + } + + public static function suite() + { + return new ezcGraphDatabaseTieinSuite; + } +} +?> diff --git a/include/ezcomponents/ImageAnalysis/CREDITS b/include/ezcomponents/ImageAnalysis/CREDITS new file mode 100644 index 000000000..2cc9fc273 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/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/ImageAnalysis/ChangeLog b/include/ezcomponents/ImageAnalysis/ChangeLog new file mode 100644 index 000000000..9f454674c --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/ChangeLog @@ -0,0 +1,113 @@ +1.1.3 - Monday 05 May 2008 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed bug #12638: ezcImageAnalyzerImagemagickHandler::checkImagemagick + method missing SunOS in switch. + + +1.1.2 - Monday 02 July 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Documentation updates and fixes. + + +1.1.1 - Monday 25 June 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Documentation updates and fixes. + + +1.1 - Monday 18 December 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #9493: Convert < and > in exception messages to ' and ' for + Cache, ConsoleTools, ImageAnalysis, ImageConversion, PersistentObject, + PersistentObjectDatabaseSchemaTiein. +- Fixed issue #9819: Let all components deal with the ezcBaseAutoloadException + properly. + + +1.1beta1 - Monday 20 November 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #9013: SVG not detected in + ezcImageImagemagickBaseHandler::determineTypes(). + + +1.0.1 - Monday 27 February 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed unreported bug: ezcImageAnalyzerImagemagickHandler threw old exception + style in isAvailable() if the operating system is not supported. + + +1.0 - Monday 30 January 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed bug #7606: Fatal error without exif-extension. Exif information is + only provided when extension is available. + + +1.0rc1 - Monday 16 January 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added ezcImageAnalyzerHandler class (abstract base class for all handlers). +- Added ezcImageAnalyzerPhpHandler (implementation of the original + ImageAnalysis functionality in this handler). +- Added ezcImageAnalyzerImagemagickHandler (still to implement, will use + ImageMagick's "identify" binary to analyze a file). +- Implemented automatic choosing and loading of a feasible handler in + ezcImageAnalyzer. +- Added static getHandlerClasses() and setHandlerClasses() methods on + ezcImageAnalyzer to manipulate the handler classes. +- Added ezcImageAnalyzerData struct to store the data gathered by an + ezcImageAnalyzerHandler class. + +- Changed exception behavior. All errors will now throw a different exception + class. +- Changed ImageAnalysis to be able to use multiple handlers (to be capable of + analyzing more formats). +- Changed ezcImageAnalyzer so that the data provided it provides is now + accessed through $analyzer->data->value instead of $analyzer->value. + + +1.0beta2 - Friday 23 December 2005 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +ezcImageAnalyzer +================ + +- Changed class so that all information is available through overloaded + properties. +- Changed analyzeGeneric() to use getimagesize() which does not rely on any + PHP extensions. +- Changed the GIF scanning code to the one currently used in eZ publish 3.x, + this scans each GIF block properly and looks for interesting information. + The GIF analyzer will now in addition set the properties: + + * width + * height + * mode + * transparencyType + * comment + * commentList + * colorCount + +- Changed method names: + + * processBasics() to analyzeType(). + * processExif() to analyzeExif(). + +- Added hasThumbnail property which reports whether the image has thumbnails + available. + +- Removed the methods getData(), getExtra(), getMime(). This data can now be + accessed through properties instead. + +- Fixed property list for GIFs, and made sure 'size' is set. + + +1.0beta1 - Thursday 24 November 2005 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Initial release of this package. diff --git a/include/ezcomponents/ImageAnalysis/DESCRIPTION b/include/ezcomponents/ImageAnalysis/DESCRIPTION new file mode 100644 index 000000000..eadaeb820 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/DESCRIPTION @@ -0,0 +1,3 @@ +This class allows you to analyse image files in different ways. At least the +MIME type of the file is returned. In some cases (JPEG, TIFF and GIF) +additional information is gathered as well. diff --git a/include/ezcomponents/ImageAnalysis/design/class_diagram.png b/include/ezcomponents/ImageAnalysis/design/class_diagram.png new file mode 100644 index 000000000..1d24145bb Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/design/class_diagram.png differ diff --git a/include/ezcomponents/ImageAnalysis/design/imageanalysis.png b/include/ezcomponents/ImageAnalysis/design/imageanalysis.png new file mode 100644 index 000000000..51cadb00e Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/design/imageanalysis.png differ diff --git a/include/ezcomponents/ImageAnalysis/design/imageanalysis.xml b/include/ezcomponents/ImageAnalysis/design/imageanalysis.xml new file mode 100644 index 000000000..e2501d95b --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/design/imageanalysis.xml @@ -0,0 +1,379 @@ + + + + + Enterprise Architect + 2.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Package + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/include/ezcomponents/ImageAnalysis/docs/example.php b/include/ezcomponents/ImageAnalysis/docs/example.php new file mode 100644 index 000000000..6a00081bb --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/docs/example.php @@ -0,0 +1,32 @@ +mime == 'image/tiff' || $image->mime == 'image/jpeg' ) +{ + // Analyzation of further image data is done during access of the data + echo 'Photo taken on '.date( 'Y/m/d, H:i', $image->data->date ).".\n"; +} +elseif ( $mime !== false ) +{ + echo "Format was detected as {$mime}.\n"; +} +else +{ + echo "Unknown photo format.\n"; +} + +?> diff --git a/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_01.jpg b/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_01.jpg new file mode 100644 index 000000000..5561c23f6 Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_01.jpg differ diff --git a/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_02.jpg b/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_02.jpg new file mode 100644 index 000000000..b13ab10aa Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_02.jpg differ diff --git a/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_03.jpg b/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_03.jpg new file mode 100644 index 000000000..963c35c94 Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/docs/img/imageanalysis_example_03.jpg differ diff --git a/include/ezcomponents/ImageAnalysis/docs/toby.jpg b/include/ezcomponents/ImageAnalysis/docs/toby.jpg new file mode 100644 index 000000000..a632ae0f7 Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/docs/toby.jpg differ diff --git a/include/ezcomponents/ImageAnalysis/docs/tutorial.txt b/include/ezcomponents/ImageAnalysis/docs/tutorial.txt new file mode 100644 index 000000000..42bd704e8 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/docs/tutorial.txt @@ -0,0 +1,126 @@ +eZ Components - ImageAnalysis +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. contents:: Table of Contents + +Introduction +============ + +The ImageAnalysis component allows you to analyze certain image attributes. + +Class overview +============== + +ezcImageAnalyzer is the main class for this component. It is responsible for +handling the analysis of image files, as well as caching the results. + +Usage +===== + +MIME type determination +----------------------- + +The following example simply detects the MIME type of an image and prints it: + +.. include:: tutorial_example_01.php + :literal: + +On line 5, a new ezcImageAnalyzer object is instantiated. This must be done for +each image to be analyzed. In line 7, the MIME type is determined. Here is an +example image including the output: + +.. image:: img/imageanalysis_example_01.jpg + :alt: Simple JPEG image. + +:: + + Image has MIME type + +Extracting further data +----------------------- + +Aside from the MIME type, ImageAnalysis can extract other image information. +The following example demonstrates this: + +.. include:: tutorial_example_02.php + :literal: + +The example is basically the same as the first one, except that more data is requested +from ezcImageAnalyzer (lines 8 to 11). The analysis of additional data +begins on line 9. After that, the data is cached in the ezcImageAnalyzer object. + +The width, height and size values are available for every analyzable image. +A comment is not always available. If an image property is not available, the +output will be some sensible default value (such as n/a). (Note that the +availability of some data also depends on the availability of PHP's Exif +extension.) + +The example image and printed output is shown below: + +.. image:: img/imageanalysis_example_02.jpg + +:: + + Image data: + MIME type: image/jpeg + Width: 380 px + Height: 285 px + Filesize: 25984 b + Comment: n/a + + +Configuring handlers +-------------------- + +Like ezcImageConverter, ezcImageAnalyzer is based on handler classes, which +allow it to utilize different back-ends for image analysis. The currently +implemented handlers are: + +ezcImageAnalyzerPhpHandler + This uses PHP's getimagesize() function (which does not require the GD + extension!) and can optionally use PHP's Exif extension. + +ezcImageAnalyzerImagemagickHandler + Here `ImageMagick`_'s "identify" program is used. + +Both handlers are activated by default and are capable of determining if their +preconditions are fulfilled. + +You might need to configure a handler, if for example the path to the +ImageMagick_ "identify" binary is not available in the $PATH environment +variable. The following example shows how this is possible and what else can be +configured for the handlers: + +.. _ImageMagick: http://www.imagemagick.org/script/index.php + +.. include:: tutorial_example_03.php + :literal: + +Basically, the code is the same as in example 2, except that ezcImageAnalyzer +is being configured to only use its ImageMagick handler and +not the PHP handler. In addition, the location of the "identify" binary is +explicitly set. See the results below: + +.. image:: img/imageanalysis_example_03.jpg + +:: + + Image data: + MIME type: image/jpeg + Width: 320 px + Height: 240 px + Filesize: 26365 b + Comment: San Francisco airport, October 2005. + +More information +================ + +For more information, see the ezcImageAnalyzer API documentation. + + +.. + Local Variables: + mode: rst + fill-column: 79 + End: + vim: et syn=rst tw=79 diff --git a/include/ezcomponents/ImageAnalysis/docs/tutorial_autoload.php b/include/ezcomponents/ImageAnalysis/docs/tutorial_autoload.php new file mode 100644 index 000000000..66b1dcf1e --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/docs/tutorial_autoload.php @@ -0,0 +1,20 @@ + diff --git a/include/ezcomponents/ImageAnalysis/docs/tutorial_example_01.php b/include/ezcomponents/ImageAnalysis/docs/tutorial_example_01.php new file mode 100644 index 000000000..ca23110b6 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/docs/tutorial_example_01.php @@ -0,0 +1,8 @@ +mime}>.\n"; +?> diff --git a/include/ezcomponents/ImageAnalysis/docs/tutorial_example_02.php b/include/ezcomponents/ImageAnalysis/docs/tutorial_example_02.php new file mode 100644 index 000000000..52c4fec23 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/docs/tutorial_example_02.php @@ -0,0 +1,15 @@ +mime}\n"; +echo "Width:\t\t{$image->data->width} px\n"; +echo "Height:\t\t{$image->data->height} px\n"; +echo "Filesize:\t{$image->data->size} b\n"; + +$comment = ( $image->data->comment == '' ) ? 'n/a' : $image->data->comment; +echo "Comment:\t{$comment}\n"; +?> diff --git a/include/ezcomponents/ImageAnalysis/docs/tutorial_example_03.php b/include/ezcomponents/ImageAnalysis/docs/tutorial_example_03.php new file mode 100644 index 000000000..109786042 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/docs/tutorial_example_03.php @@ -0,0 +1,21 @@ + array( 'binary' => '/usr/bin/identify' ), + ) +); + +$image = new ezcImageAnalyzer( $tutorialPath.'/img/imageanalysis_example_03.jpg' ); + +echo "Image data:\n"; +echo "MIME type:\t{$image->mime}\n"; +echo "Width:\t\t{$image->data->width} px\n"; +echo "Height:\t\t{$image->data->height} px\n"; +echo "Filesize:\t{$image->data->size} b\n"; + +$comment = ( $image->data->comment == '' ) ? 'n/a' : $image->data->comment; +echo "Comment:\t{$comment}\n"; +?> diff --git a/include/ezcomponents/ImageAnalysis/src/analyzer.php b/include/ezcomponents/ImageAnalysis/src/analyzer.php new file mode 100644 index 000000000..da1538938 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/src/analyzer.php @@ -0,0 +1,354 @@ + + * // Analyzation of the MIME type is done during creation. + * $image = new ezcImageAnalyzer( dirname( __FILE__ ).'/toby.jpg' ); + * + * if ( $image->mime == 'image/tiff' || $image->mime == 'image/jpeg' ) + * { + * // Analyzation of further image data is done during access of the data + * echo 'Photo taken on '.date( 'Y/m/d, H:i', $image->data->date ).".\n"; + * } + * elseif ( $mime !== false ) + * { + * echo "Format was detected as {$mime}.\n"; + * } + * else + * { + * echo "Unknown photo format.\n"; + * } + * + * + * If you want to manipulate the handlers used by ezcImageAnalyzer, you can do + * this globally like this: + * + * // 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, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + ' ', + $analyzer->data->comment, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 76383, + $analyzer->data->size, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + true, + $analyzer->data->hasThumbnail, + ' not extracted correctly for JPEG.' + ); + } + + public function testImagemagickHandlerJpegExifReportsDetails() + { + $file = $this->basePath . $this->testFiles['exif_jpeg']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + 399, + $analyzer->data->width, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for JPEG.' + ); + // @FIXME: update test case, as soon as Exif works here. + $this->assertSame( + null, + $analyzer->data->comment, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 76383, + $analyzer->data->size, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for JPEG.' + ); + // @FIXME: update test case, as soon as Exif works here. + /* + $this->assertSame( + true, + $analyzer->data->hasThumbnail, + ' not extracted correctly for JPEG.' + ); + */ + } + + public function testPhpHandlerJpegNoexifReportsDetails() + { + $file = $this->basePath . $this->testFiles['noexif_jpeg']; + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + 399, + $analyzer->data->width, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + null, + $analyzer->data->comment, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 68802, + $analyzer->data->size, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + false, + $analyzer->data->hasThumbnail, + ' not extracted correctly for JPEG.' + ); + } + + public function testImagemagickHandlerJpegNoexifReportsDetails() + { + $file = $this->basePath . $this->testFiles['noexif_jpeg']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + 399, + $analyzer->data->width, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + null, + $analyzer->data->comment, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + 68802, + $analyzer->data->size, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for JPEG.' + ); + $this->assertSame( + false, + $analyzer->data->hasThumbnail, + ' not extracted correctly for JPEG.' + ); + } + + public function testPhpHandlerTiffExifReportsDetails() + { + $file = $this->basePath . $this->testFiles['exif_tiff']; + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + 399, + $analyzer->data->width, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + null, + $analyzer->data->comment, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 108125, + $analyzer->data->size, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->hasThumbnail, + ' not extracted correctly for TIFF.' + ); + } + + public function testImagemagickHandlerTiffExifReportsDetails() + { + $file = $this->basePath . $this->testFiles['exif_tiff']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + 399, + $analyzer->data->width, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for TIFF.' + ); + // FIXME: Exif does not show comment, but ImageMagick does!!! + $this->assertSame( + 'A simple comment in a TIFF file.', + $analyzer->data->comment, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 108125, + $analyzer->data->size, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->hasThumbnail, + ' not extracted correctly for TIFF.' + ); + } + + public function testPhpHandlerTiffNoexifReportsDetails() + { + $file = $this->basePath . $this->testFiles['noexif_tiff']; + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + 399, + $analyzer->data->width, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + null, + $analyzer->data->comment, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 108043, + $analyzer->data->size, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->hasThumbnail, + ' not extracted correctly for TIFF.' + ); + } + + public function testImagemagickHandlerTiffNoexifReportsDetails() + { + $file = $this->basePath . $this->testFiles['noexif_tiff']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + 399, + $analyzer->data->width, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 600, + $analyzer->data->height, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + true, + $analyzer->data->isColor, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + null, + $analyzer->data->comment, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + 108043, + $analyzer->data->size, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for TIFF.' + ); + $this->assertSame( + false, + $analyzer->data->hasThumbnail, + ' not extracted correctly for TIFF.' + ); + } + + public function testPhpHandlerPngReportsDetails() + { + $file = $this->basePath . $this->testFiles['noanimated_png']; + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + 160, + $analyzer->data->width, + ' not extracted correctly for PNG.' + ); + $this->assertSame( + 120, + $analyzer->data->height, + ' not extracted correctly for PNG.' + ); + $this->assertSame( + 5420, + $analyzer->data->size, + ' not extracted correctly for PNG.' + ); + } + + public function testImagemagickHandlerPngReportsDetails() + { + $file = $this->basePath . $this->testFiles['noanimated_png']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + 160, + $analyzer->data->width, + ' not extracted correctly for PNG.' + ); + $this->assertSame( + 120, + $analyzer->data->height, + ' not extracted correctly for PNG.' + ); + $this->assertSame( + 5420, + $analyzer->data->size, + ' not extracted correctly for PNG.' + ); + } + + public function testPhpHandlerAnimatedGifReportsAnimated() + { + $file = $this->basePath . $this->testFiles['animated_gif']; + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + true, + $analyzer->data->isAnimated, + ' not extracted correctly for GIF.' + ); + } + + public function testImagemagickHandlerAnimatedGifReportsAnimated() + { + $file = $this->basePath . $this->testFiles['animated_gif']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + true, + $analyzer->data->isAnimated, + ' not extracted correctly for GIF.' + ); + } + + public function testPhpHandlerNoexifJpegReportsNotAnimated() + { + $file = $this->basePath . $this->testFiles['noexif_jpeg']; + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' no extracted correctly for JPEG.' + ); + } + + public function testImagemagickHandlerNoexifJpegReportsNotAnimated() + { + $file = $this->basePath . $this->testFiles['noexif_jpeg']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' no extracted correctly for JPEG.' + ); + } + + public function testPhpHandlerNonanimatedGifReportsNotAnimated() + { + $file = $this->basePath . $this->testFiles['noanimated_gif']; + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for GIF.' + ); + } + + public function testImagemagickHandlerNonanimatedGifReportsNotAnimated() + { + $file = $this->basePath . $this->testFiles['noanimated_gif']; + $analyzer = $this->getAnalyzerImagemagickHandler( $file ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for GIF.' + ); + } + + public function testPhpHandlerExifJpegReportsNotAnimated() + { + $file = $this->basePath . $this->testFiles['exif_jpeg']; + + // Test Php handler + $analyzer = $this->getAnalyzerPhpHandler( $file ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for JPEG.' + ); + } + + public function testImagemagickHandlerExifJpegReportsNotAnimated() + { + $file = $this->basePath . $this->testFiles['exif_jpeg']; + + // Test ImageMagick handler + $analyzer = $this->getAnalyzerImageMagickHandler( $file ); + $this->assertSame( + false, + $analyzer->data->isAnimated, + ' not extracted correctly for JPEG.' + ); + } + + public function testSvgMimeType() + { + $file = $this->basePath . $this->testFiles['svg']; + $analyzer = $this->getAnalyzerImageMagickHandler( $file ); + $this->assertEquals( + "image/svg+xml", + $analyzer->mime, + ' not extracted correctly for SVG.' + ); + } + + public function testAnalyzerGeneralNotProcessable() + { + + } + + protected function getAnalyzerPhpHandler( $file ) + { + ezcImageAnalyzer::setHandlerClasses( array( 'ezcImageAnalyzerPhpHandler' => array() ) ); + return new ezcImageAnalyzer( $file ); + } + + protected function getAnalyzerImageMagickHandler( $file ) + { + ezcImageAnalyzer::setHandlerClasses( array( 'ezcImageAnalyzerImagemagickHandler' => array() ) ); + return new ezcImageAnalyzer( $file ); + } + + public function testPropertiesGetInvalid() + { + $file = $this->basePath . $this->testFiles['svg']; + $analyzer = $this->getAnalyzerImageMagickHandler( $file ); + try + { + $analyzer->no_such_property; + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcBasePropertyNotFoundException $e ) + { + $expected = "No such property name 'no_such_property'."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testPropertiesSetDenied() + { + $file = $this->basePath . $this->testFiles['svg']; + $analyzer = $this->getAnalyzerImageMagickHandler( $file ); + try + { + $analyzer->mime = 'some value'; + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcBasePropertyPermissionException $e ) + { + $expected = "The property 'mime' is read-only."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testPropertiesSetInvalid() + { + $file = $this->basePath . $this->testFiles['svg']; + $analyzer = $this->getAnalyzerImageMagickHandler( $file ); + try + { + $analyzer->no_such_property = 'some value'; + $this->fail( 'Expected exception was not thrown.' ); + } + catch ( ezcBasePropertyNotFoundException $e ) + { + $expected = "No such property name 'no_such_property'."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testImagemagickHandlerNonExistentFile() + { + $fileName = $this->basePath . "no_such_file.svg"; + try + { + $analyzer = $this->getAnalyzerImageMagickHandler( $fileName ); + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcBaseFileNotFoundException $e ) + { + $expected = "The file '{$fileName}' could not be found."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testImagemagickHandlerUnreadableFile() + { + $tempDir = $this->createTempDir( 'ezcImageAnalysisAnalyzerTest' ); + $fileName = $tempDir . "/test-unreadable.svg"; + $fileHandle = fopen( $fileName, "wb" ); + fwrite( $fileHandle, "some contents" ); + fclose( $fileHandle ); + chmod( $fileName, 0 ); + + try + { + $analyzer = $this->getAnalyzerImageMagickHandler( $fileName ); + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcBaseFilePermissionException $e ) + { + $this->removeTempDir(); + $expected = "The file '{$fileName}' can not be opened for reading."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testImagemagickHandlerNotProcessableFile() + { + $tempDir = $this->createTempDir( 'ezcImageAnalysisAnalyzerTest' ); + $fileName = $tempDir . "/test-unreadable.svg"; + $fileHandle = fopen( $fileName, "wb" ); + fwrite( $fileHandle, "some contents" ); + fclose( $fileHandle ); + + try + { + $analyzer = $this->getAnalyzerImageMagickHandler( $fileName ); + $this->fail( 'Expected exception was not thrown' ); + } + catch ( ezcImageAnalyzerFileNotProcessableException $e ) + { + $this->removeTempDir(); + $expected = "Could not process file '{$fileName}'. Reason: Could not determine MIME type of file.."; + $this->assertEquals( $expected, $e->getMessage() ); + } + } + + public function testGetHandlerClasses() + { + ezcImageAnalyzer::getHandlerClasses(); + } + + public function testIsSet() + { + $file = $this->basePath . $this->testFiles['svg']; + $analyzer = $this->getAnalyzerImageMagickHandler( $file ); + $this->assertEquals( true, isset( $analyzer->mime ) ); + $this->assertEquals( true, isset( $analyzer->data ) ); + $this->assertEquals( false, isset( $analyzer->no_such_property ) ); + } +} +?> diff --git a/include/ezcomponents/ImageAnalysis/tests/data/gif_animated.gif b/include/ezcomponents/ImageAnalysis/tests/data/gif_animated.gif new file mode 100644 index 000000000..4f59d06c0 Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/tests/data/gif_animated.gif differ diff --git a/include/ezcomponents/ImageAnalysis/tests/data/gif_nonanimated.gif b/include/ezcomponents/ImageAnalysis/tests/data/gif_nonanimated.gif new file mode 100644 index 000000000..4bb071b58 Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/tests/data/gif_nonanimated.gif differ diff --git a/include/ezcomponents/ImageAnalysis/tests/data/jpeg_exif.jpg b/include/ezcomponents/ImageAnalysis/tests/data/jpeg_exif.jpg new file mode 100644 index 000000000..a632ae0f7 Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/tests/data/jpeg_exif.jpg differ diff --git a/include/ezcomponents/ImageAnalysis/tests/data/jpeg_noexif.jpg b/include/ezcomponents/ImageAnalysis/tests/data/jpeg_noexif.jpg new file mode 100644 index 000000000..ac8cb34f5 Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/tests/data/jpeg_noexif.jpg differ diff --git a/include/ezcomponents/ImageAnalysis/tests/data/png_nonanimated.png b/include/ezcomponents/ImageAnalysis/tests/data/png_nonanimated.png new file mode 100644 index 000000000..9a59b282d Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/tests/data/png_nonanimated.png differ diff --git a/include/ezcomponents/ImageAnalysis/tests/data/svg.svg b/include/ezcomponents/ImageAnalysis/tests/data/svg.svg new file mode 100644 index 000000000..91c2ac575 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/tests/data/svg.svg @@ -0,0 +1,62 @@ + + + + + + + + + image/svg+xml + + + + + + + + diff --git a/include/ezcomponents/ImageAnalysis/tests/data/tiff_exif.tiff b/include/ezcomponents/ImageAnalysis/tests/data/tiff_exif.tiff new file mode 100644 index 000000000..789bff35b Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/tests/data/tiff_exif.tiff differ diff --git a/include/ezcomponents/ImageAnalysis/tests/data/tiff_noexif.tiff b/include/ezcomponents/ImageAnalysis/tests/data/tiff_noexif.tiff new file mode 100644 index 000000000..379b4a80e Binary files /dev/null and b/include/ezcomponents/ImageAnalysis/tests/data/tiff_noexif.tiff differ diff --git a/include/ezcomponents/ImageAnalysis/tests/suite.php b/include/ezcomponents/ImageAnalysis/tests/suite.php new file mode 100644 index 000000000..f150d1890 --- /dev/null +++ b/include/ezcomponents/ImageAnalysis/tests/suite.php @@ -0,0 +1,37 @@ +setName( "ImageAnalysis" ); + $this->addTest( ezcImageAnalysisAnalyzerTest::suite() ); + } + + public static function suite() + { + return new ezcImageAnalysisSuite( "ezcImageAnalysisSuite" ); + } +} +?> diff --git a/include/ezcomponents/ImageConversion/CREDITS b/include/ezcomponents/ImageConversion/CREDITS new file mode 100644 index 000000000..2cc9fc273 --- /dev/null +++ b/include/ezcomponents/ImageConversion/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/ImageConversion/ChangeLog b/include/ezcomponents/ImageConversion/ChangeLog new file mode 100644 index 000000000..2fcdc9179 --- /dev/null +++ b/include/ezcomponents/ImageConversion/ChangeLog @@ -0,0 +1,279 @@ +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. + + +1.3.3 - Monday 17 December 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #12166: Undefined attribute in ezcImageImagemagickBaseHandler. + + +1.3.2 - Wednesday 05 December 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #12126: ImageConversion test is incorrect. +- Fixed issue #12160: Conversion of transparent backgrounds does not work on + certain systems. +- Fixed issue #12171: Monochrome colorspace conversion does not work on some + systems with the ImageMagick handler. + + +1.3.1 - Wednesday 28 November 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #11029: Converting transparent PNG to JPG cause an strange + effect. A new option $transparencyReplacementColor has been introduced to + ezcImageSaveOptions, which will be utilized if a potentially tranparent + image is converted to a format that does not support a transparent + background. + + +1.3 - Monday 02 July 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #11030: The GD-Handler's watermarkPercent() calculates + incorrectly. The placement and size of the watermark are now calculated + correctly. +- Documentation updates and fixes. + + +1.3rc1 - Monday 25 June 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #10949: Rename PHP error if file already exists. If rename() + fails on Windows systems, the temporary files are cleaned up properly now. +- Documentation updates and fixes. + + +1.3beta2 - Thursday 31 May 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed exception messages. +- Updated documentation for ezcImageThumbnailFilters. + + +1.3beta1 - Monday 07 May 2007 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Implemented feature #8589: JPEG quality transform for ImageConversion. A new + option class ezcImageSaveOptions supports setting a quality indicator for + JPEG and a compression indicator for PNG images. +- Implemented feature #9564: Watermark (and perhaps other filters) should + allow image placements from opposite corners. Crop filters support negative + offsets now, too. + + +1.2 - Monday 18 December 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed issue #9493: Convert < and > in exception messages to ' and ' for + Cache, ConsoleTools, ImageAnalysis, ImageConversion, PersistentObject, + PersistentObjectDatabaseSchemaTiein. +- Fixed issue #9520: ImageConversion test fails. +- Fixed issue #9521: Watermark tests needs some tuning. +- Fixed issue #9563: watermarkAbsolute filters requires width and height options. +- Fixed issue #9819: Let all components deal with the ezcBaseAutoloadException + properly. + + +1.2beta1 - Monday 20 November 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added feature #9236: Added two new filters for adding watermarks to images. +- Fixed issue #8892: Handler / Converter destruction should remove all temp + files. +- Fixed issue #9012: Problems with open_basedir. +- Fixed issue #9013: SVG not detected in + ezcImageImagemagickBaseHandler::determineTypes(). +- Fixed issue #9014: ezcImageTransformationException doesn't accept + ezcImageAnalyzerException as argument. +- Fixed issue #9440: GD scale/crop filters loose transparency. + + +1.1.2 - Monday 09 October 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed bug #8958: GD crop filter calculates width and height incorrect. +- Fixed bug #8959: GD and ImageMagick crop filters behave differently. The GD + driver now behaves like the ImageMagick driver and reduces the crop + dimensions, if they are larger than the source image dimensions. + + +1.1.1 - Monday 28 August 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed bug #8712: The x and y parameters for cropping where handled as + coordinates and not as offsets. +- Refactored to use new ezcBaseStruct and properties approach. + + +1.1 - Monday 12 June 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes at all. + + +1.1rc1 - Monday 29 May 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- No changes at all. + + +1.1beta1 - Wednesday 19 April 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added checks for file names that could cause potential security issues. + File names containing one of ', " or $ will be rejected to load/save with an + ezcImageFileNameInvalidException. (See + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=345238). +- Refactored internal filter handling to make ezcImageHandler classes + publically available. +- Fixed bug #8137: ImageConversion - ezcImageTransformation fails on + processing Multiple images in 1 request. + + +1.0.1 - Thursday 23 February 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed unreported bug: ezcImageTransformation::setFilters() threw old style + exceptions on an invalid filter class. Now throws the correct + ezcBaseSettingValueException. +- Added checks for file names that could cause potential security issues. File + names containing one of the three characters ' " or $ will be rejected to + load/save with an ezcImageFileNameInvalidException. (See + http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=345238). + + +1.0 - Monday 30 January 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed unreported bug: ezcImageTransformation will cause an error, if no + filters are submitted, but only a MIME type conversion is set. +- Fixed unreported bug: ImageAnalysis and ImageConversion used different + MIME types for PSD files. +- Fixed unreported bug: Missing sanity checks for file existance and + reabability in ezcImageTransformation::transform(). + + +1.0rc1 - Monday 16 January 2006 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added more MIME types to ezcImageImagemagickHandler to be able to + transform more formats. +- Added option for ezcImageImagemagickHandler to allow the explicit setting of + ImageMagick's "convert" binary. + +- Changed exception behavior. All errors will now throw a different exception + class. +- Changed OS detection of ezcImageImagemagickHandler to better detect Windows + versions. +- Changed behavior of ezcImageGeometryFilters::scale() to its + original purpose. The scale() filter now allows scaling of images to fit + into a certain rectengular box. The former behavior is still available + throug the scaleExact() filter. +- Changed execution of ImageMagick binary from exec() to proc_open(). This + avoids nasty error output on the console if something fails. Errors from + STDERR are now caught and used in the thrown exception for analysis. + +- Fixed bug #7640: "createTransformation aspect ratio scaling". + The ImageMagick driver now does correct scaling, maintaining aspect ratio. +- Fixed unreported bug, that $name attribute in ezcImageMethodcallHandler is + private, but accessed by it's derived classes for error reporting. +- Fixed unreported bug in crop filter code of ezcImageImagemagickHandler, that + caused the crop to always start at the coordinates 0, 0. + + +1.0beta2 - Friday 23 December 2005 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Added struct for ezcImageConverter settings: ezcImageConverterSettings. +- Added new method getIdentifier() to handler classes which returns + a lowercase version of the name. The convert class uses this instead of + lowercasing the name manually. +- Added ezcImageFilter struct to keep the filter name and + options. This replaces the associative arrays and separate parameters used + throughout the various classes (mostly ezcImageTransformation). + +- Changed settings handling in ezcImageConverter to new struct + ezcImageConverterSettings. +- Changed ezcImageHandler objects to get their $name and $identifier in + their constructor. +- Changed ezcImageHandlers not to automagically find the correct class name for + a filter. Instead the ezcImageFilters object is now created by the specific + ezcImageHandler class. + +- Changed functions to properties: + + * ezcImageHandler::getName() to property 'name'. + * ezcImageHandler::getIdentifier() to property 'identifier'. + +- Changed class names: + + * ezcImageHandlerGd to ezcImageGdHandler. + * ezcImageHandlerShell to ezcImageShellHandler. + * ezcImageFiltersGd to ezcImageGdFilters. + * ezcImageFiltersShell to ezcImageShellFilters. + * ezcImageFiltersInterfaceEffect to ezcImageEffectFilters. + * ezcImageFiltersInterfaceGeomtry to ezcImageGeometryFilters. + * ezcImageFiltersInterfaceColorspace to ezcImageColorspaceFilters. + * ezcImageShellHandler to ezcImageImagemagickHandler. + * ezcImageShellFilters to ezcImageImagemagickFilters. + +- Changed function names: + + * ezcImageHandler::listFilters() to getFilterNames(). + +- Changed ezcImageTransformation::transform() to throw a catch-all exception + instead of all inherited ones. The catch-all exception stores the source + exception in it's 'parent' property. +- Changed visibility of filter handling classes to private. + The code for the filter handling needs to more work before + becoming public. Any references to the filter classes from public + classes are also removed. This marks the GD and Imagemagick handlers as + private. +- Changed ezcImageHandler::listFilters() to use the getFilters() method on + the filter object to get filter list. + +- Changed the way on how handlers can be referenced. The class names which are + passed in ezcImageConverterSettings are now stored in the + ezcImageHandlerSettings class: + + * Added a reference name setting which are used by the converter + and handler. This allows the developer to reference the handler + with a simple name. + * Add a settings array which can be used by the handler as configuration. + +- Removed 'identifier' from ezcImageHandler and uses class name of handler + for lookup in ezcImageConverter::applyFilter(). +- Removed generation from class names out of values in + ezcImageConverterSettings. Instead they should now pass the full class name + to the converter. This removes the need to prefix custom made handler + classes with ezcImageHandler. +- Removed all the special code from the abstract class ezcImageHandler and + added them to ezcImageMethodcallHandler. The main handler class is now much + cleaner and exposes less special functions and properties to the public. + + * Updated GD and ImageMagick handlers to extends the new class. + +- Fixed createTransformation() to return the transformation object. + + +1.0beta1 - Thursday 24 November 2005 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Initial release of this package. diff --git a/include/ezcomponents/ImageConversion/DEPS b/include/ezcomponents/ImageConversion/DEPS new file mode 100644 index 000000000..000647061 --- /dev/null +++ b/include/ezcomponents/ImageConversion/DEPS @@ -0,0 +1 @@ +ImageAnalysis: 1.1.3 diff --git a/include/ezcomponents/ImageConversion/DESCRIPTION b/include/ezcomponents/ImageConversion/DESCRIPTION new file mode 100644 index 000000000..4bd987631 --- /dev/null +++ b/include/ezcomponents/ImageConversion/DESCRIPTION @@ -0,0 +1,2 @@ +A set of classes to apply different filters on images, such as colour changes, +resizing and special effects. diff --git a/include/ezcomponents/ImageConversion/TODO b/include/ezcomponents/ImageConversion/TODO new file mode 100644 index 000000000..f2a0f47f8 --- /dev/null +++ b/include/ezcomponents/ImageConversion/TODO @@ -0,0 +1 @@ +- Optimization of colorspace conversions in GD handler as described here http://lists.ez.no/pipermail/components/2005-December/000819.html. diff --git a/include/ezcomponents/ImageConversion/design/class_diagram.png b/include/ezcomponents/ImageConversion/design/class_diagram.png new file mode 100644 index 000000000..8f641419f Binary files /dev/null and b/include/ezcomponents/ImageConversion/design/class_diagram.png differ diff --git a/include/ezcomponents/ImageConversion/design/design.htm b/include/ezcomponents/ImageConversion/design/design.htm new file mode 100644 index 000000000..d851ef49d --- /dev/null +++ b/include/ezcomponents/ImageConversion/design/design.htm @@ -0,0 +1,157 @@ + + + + + + +eZ publish Enterprise Component: ImageConversion, Design + + + +

eZ publish Enterprise Component: ImageConversion, Design

+
+
+

Introduction

+

Find more info about the requirenments on which this design is based on in the +requirenments.txt.

+
+
+

Design description

+
+

ezcImageManager

+

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.

+
+
+

ezcImageHandler

+

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.

+
+
+

ezcImageFilter

+

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

+

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.

+
+

Example 1

+ ++++ + + + + + + + + + + + + + + +
Transformation:Preview
Reference:__original__
MIME:

image/JPEG

+

image/PNG

+
Filters:scale 400x400
+
+
+

Example 2

+ ++++ + + + + + + + + + + + + + + +
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.

+
+
+

Example 3

+ ++++ + + + + + + + + + + + + + + +
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.

+
+

^L

+

Local Variables: +mode: rst +indent-tabs-mode: nil +sentence-end-double-space: t +fill-column: 70 +End: +vim: et syn=rst tw=78 wrap

+
+
+
+
+
+ + diff --git a/include/ezcomponents/ImageConversion/design/design.txt b/include/ezcomponents/ImageConversion/design/design.txt new file mode 100644 index 000000000..d1a182282 --- /dev/null +++ b/include/ezcomponents/ImageConversion/design/design.txt @@ -0,0 +1,218 @@ +eZ publish Enterprise Component: ImageConversion, Design +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Introduction +============ + +Purpose of ImageConversion package +---------------------------------- + +The ImageConversion package will be utilized to convert existing images +in different ways: + +- Conversion between MIME types (e.g. image/BMP -> image/JPEG,...) +- Resizing image files (e.g. scaling, cropping,...) +- Filtering image content (e.g. change of color pallet, add noise,...) + +Current implementation +---------------------- + +Currently the described functionality is implemented utilizing the following +classes, inside the library lib/ezimage/: + +eZImageManager + Controller for the other classes utilized in this library. Handles + configuration, dispatches the necessary action to the different classes. + +eZImageHandler + Baseclass for image handlers. Provides common methods between image + handlers and defines a common API. + +eZImageGDHandler + Image handler implementation for ext/GD2. Extends eZImageHandler. + +eZImageShellHandler + Image handler implementation for Image Magick. Extends eZImageHandler. + +General notes +------------- + +The idea behind the design chosen for this class is definitly the right one, +but it could have been much more modulized to gain a more clean code layout +and more flexibillities. Beside that, the user interface seems pretty unclear +and needs some general revision. + +Requirenments +============= + +Design goals +------------ + +Several goals have to be kept in mind while re-designing the implemented +functionality: + +- Do not reduce possibilities, but enhance them. +- Create a more clean user interface and enhance usabillity. +- Raise flexibillity regarding backends and filter definition. +- Keep the code fast. + +Detailed requirenments +---------------------- + +The ImageConversion component allows to deal with quite complex image +conversions in an easy to use ways. Beside that it handles automatic +conversions, if necessary, like converting incoming image formats to a range +of range of wanted output formats. This paragraph tries to summarize, what +exactly has to be done by ImageConversion: + +Conversion between image formats + In general, ImageConversion should be able to convert images between MIME + types utilizing it's backends. Conversions must be globally defineable to + allow forcing of conversions (like for GIF -> PNG). Since some image + formats have special cases, in which a conversion is not possible (like + animated GIF), it must be possible to define exceptions for conversion. + Beside the global conversion, it has to be possible, to convert images + explicitly. Format conversions can ba parameterized (like the compression + factor for JPEG, the colorpallet for GIF,...). The conversions possible + depend on the image handlers available. + + +Filtering of image contents + Filtering of images can have a lot of incarnation in this case: + + Geometry manipulation + down/up only, keep/change ratio, with/height only,... + + + Attribute manipulation (was: colorspace) + transform color space, change quality,... + + + Content manipulation + adding noise, swirrling, adding borders... + + + Which filters are available depends highly on the available image + handlers, their version and maybe other factors in respect to them (PHP + version,...). + + Every filter can have a variaty of options and settings to influence its + behaviour. Options and settings maybe completly different between filters. + + +Definition of image formats + To reduce the overhead of manually defining, which filters have to be + applied to an image to achieve a certain goal (like "create a + thumbnail", "create a preview", "make it look like an old photo",...), the + definition of different image formats should be allowed. A format + definition can include all of the above stated transformations. + + Beside that, it has to be possible to define, that a format is based on + another format (like a thumbnail should be created from a preview, to + reduce conversion ammount). If a format references another format, this + reference format will be created first (if it does not exist) and + conversion to the target format will take place afterwards. This allows to + define a tree of format conversions. Every node of the tree should be + saved for later utilization. + + Currently, this formats are called aliases. + +Design +====== + +ezcImageConverter +----------------- + +The main class of the component is the ezcImageConverter, which dispatches the +actions performed on images, holds the ezcImageHandler's (which actually perform +the actions) and manages / defines the ezcImageTransformations which hold +conversions and filters. + +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. + +ezcImageHandler +--------------- + +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. + +ezcImageFilters +--------------- + +[[[------UPDATE NEEDED HERE!--------]]] + +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 +---------------------- + +ezcImageTransformation's abstract image types to allow the easy combination of +conversions between MIME types and filters which are necessary to get a desired image type. +For conversions only the target MIME type is necessary. + +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. + +Example 1 +^^^^^^^^^ + +====================== ======================= +Transformation: Preview +MIME: image/JPEG + + image/PNG +Filters: scale 400x400 +====================== ======================= + +Example 2 +^^^^^^^^^ + +====================== ======================= +Transformation: Thumbnail +MIME: image/JPEG + + image/PNG +Filters: scale 100x100 + + colorspace grey +====================== ======================= + +Will scale down the image to 100x100 pixels and convert it to greyscale. + +Example 3 +^^^^^^^^^ + +====================== ======================= +Transformation: OldPhotos +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. + +^L +.. +Local Variables: +mode: rst +indent-tabs-mode: nil +sentence-end-double-space: t +fill-column: 79 +End: +vim: et syn=rst tw=79 wrap diff --git a/include/ezcomponents/ImageConversion/design/image.png b/include/ezcomponents/ImageConversion/design/image.png new file mode 100644 index 000000000..a287acfc4 Binary files /dev/null and b/include/ezcomponents/ImageConversion/design/image.png differ diff --git a/include/ezcomponents/ImageConversion/design/image.xml b/include/ezcomponents/ImageConversion/design/image.xml new file mode 100644 index 000000000..04e7d791b --- /dev/null +++ b/include/ezcomponents/ImageConversion/design/image.xml @@ -0,0 +1,4571 @@ + + + + + Enterprise Architect + 2.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Package + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/include/ezcomponents/ImageConversion/design/image_exceptions.png b/include/ezcomponents/ImageConversion/design/image_exceptions.png new file mode 100644 index 000000000..0fa74dc0f Binary files /dev/null and b/include/ezcomponents/ImageConversion/design/image_exceptions.png differ diff --git a/include/ezcomponents/ImageConversion/design/image_filters.png b/include/ezcomponents/ImageConversion/design/image_filters.png new file mode 100644 index 000000000..873e1777a Binary files /dev/null and b/include/ezcomponents/ImageConversion/design/image_filters.png differ diff --git a/include/ezcomponents/ImageConversion/docs/example_general.php b/include/ezcomponents/ImageConversion/docs/example_general.php new file mode 100644 index 000000000..4ebe5ef1e --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/example_general.php @@ -0,0 +1,66 @@ + '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' ); + +echo 'Succesfully converted <'. dirname( __FILE__ ). '/jpeg.jpg> to <'.dirname( __FILE__ ). '/jpeg_thumb.jpg'.">\n"; +?> diff --git a/include/ezcomponents/ImageConversion/docs/example_singleton.php b/include/ezcomponents/ImageConversion/docs/example_singleton.php new file mode 100644 index 000000000..393a3d4c0 --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/example_singleton.php @@ -0,0 +1,90 @@ + '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' ); + +echo 'Succesfully converted <'. dirname( __FILE__ ). '/jpeg.jpg> to <'. dirname( __FILE__ ). '/jpeg_singleton.jpg'.">\n"; +?> diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_01_after.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_01_after.jpg new file mode 100644 index 000000000..a97c9b800 Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_01_after.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_01_before.bmp b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_01_before.bmp new file mode 100644 index 000000000..bac829b3a Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_01_before.bmp differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_02_after.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_02_after.jpg new file mode 100644 index 000000000..5b53ec173 Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_02_after.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_02_before.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_02_before.jpg new file mode 100644 index 000000000..d46592f79 Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_02_before.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_03_after.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_03_after.jpg new file mode 100644 index 000000000..34d2e7781 Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_03_after.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_03_before.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_03_before.jpg new file mode 100644 index 000000000..b6747996e Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_03_before.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_04_after.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_04_after.jpg new file mode 100644 index 000000000..34bdc30dd Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_04_after.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_04_before.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_04_before.jpg new file mode 100644 index 000000000..549b4b384 Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_04_before.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_05_after.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_05_after.jpg new file mode 100644 index 000000000..515134345 Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_05_after.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_05_before.jpg b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_05_before.jpg new file mode 100644 index 000000000..701873a8c Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/imageconversion_example_05_before.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/img/watermark.png b/include/ezcomponents/ImageConversion/docs/img/watermark.png new file mode 100644 index 000000000..797a0d9c4 Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/img/watermark.png differ diff --git a/include/ezcomponents/ImageConversion/docs/jpeg.jpg b/include/ezcomponents/ImageConversion/docs/jpeg.jpg new file mode 100644 index 000000000..1646b523c Binary files /dev/null and b/include/ezcomponents/ImageConversion/docs/jpeg.jpg differ diff --git a/include/ezcomponents/ImageConversion/docs/tutorial.txt b/include/ezcomponents/ImageConversion/docs/tutorial.txt new file mode 100644 index 000000000..f04dfdb65 --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/tutorial.txt @@ -0,0 +1,243 @@ +eZ Components - ImageConversion +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. contents:: Table of Contents + :depth: 2 + +Introduction +============ + +The ImageConversion component provides image manipulation tools. It +enables you to perform filter actions (such as scaling, changing the +colorspace, adding a swirl effect) and to convert between different MIME image +types. Filters and conversions are grouped as +"transformations", which can be globally configured and accessed from anywhere +in the application. Conversions and filters can be performed through different +handlers (currently supported are PHP's GD extension and the external ImageMagick +program). ImageConversion is capable of automatically selecting an appropriate +handler. You can also set handler priorities. + +Class overview +============== + +This section gives you an overview of the main classes. + +ezcImageConverter + This is the main class that collects all transformations, communicates with + the handlers and applies filters and conversions. + +ezcImageFilter + This class is used to represent the configuration of a filter. + +ezcImageTransformation + A transformation can contain any number of filters. It also specifies which + output MIME types are acceptable for the transformation. + + +Usage +===== + +Converting MIME types +--------------------- + +The following example creates a very simple transformation to convert any other +image type into JPEG: + +.. include:: tutorial_example_01_simpleconvert.php + :literal: + +First, the settings for ezcImageConverter are defined (lines 7 to 12) using +ezcImageConverterSettings. Whenever ezcImageConverter is +instantiated, it needs to know which handlers are available. The order in the +ezcImageHandlerSettings array defines the priority of the handlers. In this +case, ezcImageConverter will check if a given filter or conversion can be +performed by the GD handler. If not, it will check the ImageMagick handler. On +line 14, ezcImageConverter is instantiated using the defined settings. + +Line 16 shows how a transformation is created. The first parameter to +ezcImageConverter::createTransformation() defines the name of the +transformation, while the second parameter would usually contain filters (which +are not used here). Instead, just one output MIME type is defined as the third +parameter. As a result, this transformation returns images of the type +"image/jpeg". + +On lines 21 to 24, the transformation is applied. The first +parameter to ezcImageConverter::transform() contains the name of the +transformation to apply. The second one specifies the file to transform, while the +third one specifies the desired output filename. Aside from +exceptions of the type ezcBaseFileException, the ezcImageTransformation::transform() +method can only throw exceptions of the type ezcImageTransformationException, which +we catch here to print out an error message. + +The input and output images are shown below: + +=================== ==================== +|example_01_before| |example_01_after| +BMP version (92k) Converted JPEG (24k) +=================== ==================== + +.. |example_01_before| image:: img/imageconversion_example_01_before.bmp + :alt: Original BMP (92k). + +.. |example_01_after| image:: img/imageconversion_example_01_after.jpg + :alt: Converted JPEG (24k). + +Simple filtering +---------------- + +The next example shows a transformation that, in addition to the converting to +JPEG, uses a filter to scale images: + +.. include:: tutorial_example_02_simpletrans.php + :literal: + +After instantiating ezcImageConverter, we define the filters to apply. We apply +only one filter in this example. Each filter definition must be an instance of +ezcImageFilter. The first parameter to the constructor of ezcImageFilter +(ezcImageFilter::__construct()) is the name of the filter to use. The second +parameter is an array of settings for the filter. The filter name must +correspond to a method name for one of the filter interfaces: + +- ezcImageGeometryFilters +- ezcImageColorspaceFilters +- ezcImageEffectFilters + +The settings array must contain all parameters that the specific filter method +expects and the array keys must correspond to the names of the +parameters. For example, the scale filter used here is defined in +ezcImageGeometryFilters::scale(). The available image handlers support the +following filters: + +- ezcImageGdFilters + + * ezcImageGeometryFilters + * ezcImageColorspaceFilters + +- ezcImageImagemagickFilters + + * ezcImageGeometryFilters + * ezcImageColorspaceFilters + * ezcImageEffectFilters + +The filter definition shown here makes ezcImageConverter scale images to a box +of 320x240 pixels. Images will only be scaled if they are larger than the +given size, but not if they are already smaller or fit exactly. + +The rest of the example is pretty much the same as example 1. To keep the +example images web-friendly, we use a JPEG image as the source file here: + +Original image (450x246) +++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_02_before.jpg + :alt: Original JPEG image. + +Converted image (320x175) ++++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_02_after.jpg + :alt: Converted JPEG image. + +Complex transformations +----------------------- + +The next example shows a more advanced transformation and some other features: + +.. include:: tutorial_example_03_complextrans.php + :literal: + +In this example, there is a second parameter to the constructor of +ezcImageConverterSettings::__construct(), which defines explicit conversions +between MIME types (line 13). In this case, we define that GIF images should be +converted to PNG. When the transformation takes place, it will first check if an +explicit conversion has been defined for the input MIME type. If this is the +case, the explicit conversion will be performed. If not, the first available +output MIME type will be chosen. Note that you have to add the new MIME output +type "image/png" to the allowed output types of the transformation (see line +43). + +In the transformation definition we define 3 filters. Note that the +order of filters is important here. The first filter is "scale" again, +after which the colorspace of the image is reduced to greyscale. The last +filter adds a 5-pixel border with a near-white grey value to the image. + +For this web tutorial, a JPEG image is once again used as the source: + +Original image (400x300): ++++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_03_before.jpg + :alt: Original JPEG image. + +Converted image (330x250): +++++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_03_after.jpg + :alt: Converted JPEG image. + +.. _`downloaded here`: img/imageconversion_example_03.bmp + +Adding watermarks +----------------- + +A very convenient filter is the watermark filter, which allows you to place a +your personal sign onto an image to ensure your copyright being kept: + +.. include:: tutorial_example_04_watermark.php + :literal: + +This code snippet creates a simple transformation to place the watermark. The +'image' parameter contains the path to the watermark image, while posX and posY +define, where the watermark will be placed on the converted image. The +positions are defined from the bottom left corner, so in this case therer will +be 10 pixel left between the watermark and the image border. + +Original image (without watermark): ++++++++++++++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_04_before.jpg + :alt: Original JPEG image without watermark. + +Converted image (with watermark): ++++++++++++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_04_after.jpg + :alt: Converted JPEG image with watermark. + +It is also possible to get the size of the watermark image adjusted on the fly +and a second filter is available, which allows to define all values as +percentage values, in respect to the destination image. + +Creating thumbnails +------------------- + +The following example shows how to create a thumbnail from an image very +easily: + +.. include:: tutorial_example_05_thumbnail.php + :literal: + +While there is also a 'croppedThumbnail' filter available, which croppes +overhead from the scaled image, this filter fills the overhead from scaling +with the given fill color. The image is automatically scaled down to fit the +given thumbnail size. + +Original image (original size): ++++++++++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_05_before.jpg + :alt: Original JPEG image without watermark. + +Converted image (thumbnail): +++++++++++++++++++++++++++++ + +.. image:: img/imageconversion_example_05_after.jpg + :alt: Converted JPEG image with watermark. + + +.. + Local Variables: + mode: rst + fill-column: 79 + End: + vim: et syn=rst tw=79 diff --git a/include/ezcomponents/ImageConversion/docs/tutorial_autoload.php b/include/ezcomponents/ImageConversion/docs/tutorial_autoload.php new file mode 100644 index 000000000..66b1dcf1e --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/tutorial_autoload.php @@ -0,0 +1,20 @@ + diff --git a/include/ezcomponents/ImageConversion/docs/tutorial_example_01_simpleconvert.php b/include/ezcomponents/ImageConversion/docs/tutorial_example_01_simpleconvert.php new file mode 100644 index 000000000..5bce91029 --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/tutorial_example_01_simpleconvert.php @@ -0,0 +1,32 @@ +createTransformation( 'jpeg', array(), array( 'image/jpeg' ) ); + +try +{ + $converter->transform( + 'jpeg', + $tutorialPath.'/img/imageconversion_example_01_before.bmp', + $tutorialPath.'/img/imageconversion_example_01_after.jpg' + ); +} +catch ( ezcImageTransformationException $e) +{ + die( "Error transforming the image: <{$e->getMessage()}>" ); +} + + +?> diff --git a/include/ezcomponents/ImageConversion/docs/tutorial_example_02_simpletrans.php b/include/ezcomponents/ImageConversion/docs/tutorial_example_02_simpletrans.php new file mode 100644 index 000000000..c07166cb8 --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/tutorial_example_02_simpletrans.php @@ -0,0 +1,42 @@ + 320, + 'height' => 240, + 'direction' => ezcImageGeometryFilters::SCALE_DOWN, + ) + ), +); + +$converter->createTransformation( 'preview', $filters, array( 'image/jpeg' ) ); + +try +{ + $converter->transform( + 'preview', + $tutorialPath.'/img/imageconversion_example_02_before.jpg', + $tutorialPath.'/img/imageconversion_example_02_after.jpg' + ); +} +catch ( ezcImageTransformationException $e) +{ + die( "Error transforming the image: <{$e->getMessage()}>" ); +} + +?> diff --git a/include/ezcomponents/ImageConversion/docs/tutorial_example_03_complextrans.php b/include/ezcomponents/ImageConversion/docs/tutorial_example_03_complextrans.php new file mode 100644 index 000000000..af385cd97 --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/tutorial_example_03_complextrans.php @@ -0,0 +1,58 @@ + 'image/png', + ) +); + +$converter = new ezcImageConverter( $settings ); + +$filters = array( + new ezcImageFilter( + 'scale', + array( + 'width' => 320, + 'height' => 240, + 'direction' => ezcImageGeometryFilters::SCALE_DOWN, + ) + ), + new ezcImageFilter( + 'colorspace', + array( + 'space' => ezcImageColorspaceFilters::COLORSPACE_GREY, + ) + ), + new ezcImageFilter( + 'border', + array( + 'width' => 5, + 'color' => array( 240, 240, 240 ), + ) + ), +); + +$converter->createTransformation( 'oldphoto', $filters, array( 'image/jpeg', 'image/png' ) ); + +try +{ + $converter->transform( + 'oldphoto', + $tutorialPath.'/img/imageconversion_example_03_before.jpg', + $tutorialPath.'/img/imageconversion_example_03_after.jpg' + ); +} +catch ( ezcImageTransformationException $e) +{ + die( "Error transforming the image: <{$e->getMessage()}>" ); +} + +?> diff --git a/include/ezcomponents/ImageConversion/docs/tutorial_example_04_watermark.php b/include/ezcomponents/ImageConversion/docs/tutorial_example_04_watermark.php new file mode 100644 index 000000000..78c8befaa --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/tutorial_example_04_watermark.php @@ -0,0 +1,45 @@ + 'image/png', + ) +); + +$converter = new ezcImageConverter( $settings ); + +$filters = array( + new ezcImageFilter( + 'watermarkAbsolute', + array( + 'image' => $tutorialPath . '/img/watermark.png', + 'posX' => -52, + 'posY' => -25, + ) + ) +); + +$converter->createTransformation( 'watermark', $filters, array( 'image/jpeg', 'image/png' ) ); + +try +{ + $converter->transform( + 'watermark', + $tutorialPath.'/img/imageconversion_example_04_before.jpg', + $tutorialPath.'/img/imageconversion_example_04_after.jpg' + ); +} +catch ( ezcImageTransformationException $e) +{ + die( "Error transforming the image: <{$e->getMessage()}>" ); +} + +?> diff --git a/include/ezcomponents/ImageConversion/docs/tutorial_example_05_thumbnail.php b/include/ezcomponents/ImageConversion/docs/tutorial_example_05_thumbnail.php new file mode 100644 index 000000000..b76977200 --- /dev/null +++ b/include/ezcomponents/ImageConversion/docs/tutorial_example_05_thumbnail.php @@ -0,0 +1,49 @@ + 'image/png', + ) +); + +$converter = new ezcImageConverter( $settings ); + +$filters = array( + new ezcImageFilter( + 'filledThumbnail', + array( + 'width' => 100, + 'height' => 100, + 'color' => array( + 200, + 200, + 200, + ), + ) + ) +); + +$converter->createTransformation( 'thumbnail', $filters, array( 'image/jpeg', 'image/png' ) ); + +try +{ + $converter->transform( + 'thumbnail', + $tutorialPath.'/img/imageconversion_example_05_before.jpg', + $tutorialPath.'/img/imageconversion_example_05_after.jpg' + ); +} +catch ( ezcImageTransformationException $e) +{ + die( "Error transforming the image: <{$e->getMessage()}>" ); +} + +?> diff --git a/include/ezcomponents/ImageConversion/review-1.3.4.txt b/include/ezcomponents/ImageConversion/review-1.3.4.txt new file mode 100644 index 000000000..053c70c5a --- /dev/null +++ b/include/ezcomponents/ImageConversion/review-1.3.4.txt @@ -0,0 +1,4 @@ +[X] The class documentation for ezcImageFilter should mention (in the ctor) + where the names of the different filters can be found. Perhaps we + should add a list of all supported filters somewhere. +[X] Code coverage is pretty low, only 85%. diff --git a/include/ezcomponents/ImageConversion/src/converter.php b/include/ezcomponents/ImageConversion/src/converter.php new file mode 100644 index 000000000..860920420 --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/converter.php @@ -0,0 +1,527 @@ + + *
  • ezcImageGdHandler + *
      + *
    • Uses PHP's GD extension for image manipulation.
    • + *
    • Implements the following filter interfaces + *
        + *
      • {@link ezcImageGeometryFilters}
      • + *
      • {@link ezcImageColorspaceFilters}
      • + *
      + *
    • + *
    + *
  • + *
  • ezcImageImagemagickHandler + *
      + *
    • Uses the external "convert" program, contained in ImageMagick
    • + *
    • Implements the following interfaces: + *
        + *
      • {@link ezcImageGeometryFilters}
      • + *
      • {@link ezcImageColorspaceFilters}
      • + *
      • {@link ezcImageEffectFilters}
      • + *
      + *
    • + *
    + *
  • + * + * + * A general example, how to use ezcImageConversion to convert images: + * + * // 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/". + * @return string imagecreatefrom* function name. + * + * @throws ezcImageMimeTypeUnsupportedException + * If the load function for a given MIME type does not exist. + */ + private function getLoadFunction( $mime ) + { + if ( !$this->allowsInput( $mime ) ) + { + throw new ezcImageMimeTypeUnsupportedException( $mime, 'input' ); + } + return 'imagecreatefrom' . substr( strstr( $mime, '/' ), 1 ); + } + + /** + * Generate image* function out of a MIME type. + * + * @param string $mime MIME type in format "image/". + * @return string image* function name for saving. + * + * @throws ezcImageImagemagickHandler + * If the save function for a given MIME type does not exist. + */ + private function getSaveFunction( $mime ) + { + if ( !$this->allowsOutput( $mime ) ) + { + throw new ezcImageMimeTypeUnsupportedException( $mime, 'output' ); + } + return 'image' . substr( strstr( $mime, '/' ), 1 ); + } + + /** + * Creates default settings for the handler and returns it. + * The reference name will be set to 'GD'. + * + * @return ezcImageHandlerSettings + */ + static public function defaultSettings() + { + return new ezcImageHandlerSettings( 'GD', 'ezcImageGdHandler' ); + } + + +} + +?> diff --git a/include/ezcomponents/ImageConversion/src/handlers/imagemagick.php b/include/ezcomponents/ImageConversion/src/handlers/imagemagick.php new file mode 100644 index 000000000..08d261346 --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/handlers/imagemagick.php @@ -0,0 +1,769 @@ + 0' ); + } + if ( !is_int( $height ) || $height < 1 ) + { + throw new ezcBaseValueException( 'height', $height, 'int > 0' ); + } + + $dirMod = $this->getDirectionModifier( $direction ); + $this->addFilterOption( + $this->getActiveReference(), + '-resize', + $width.$dirMod.'x'.$height.$dirMod + ); + } + + /** + * 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 ezcImageImagemagickHandler::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 + * 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 scaleWidth( $width, $direction ) + { + if ( !is_int( $width ) || $width < 1 ) + { + throw new ezcBaseValueException( 'width', $width, 'int > 0' ); + } + + $dirMod = $this->getDirectionModifier( $direction ); + $this->addFilterOption( + $this->getActiveReference(), + '-resize ', + $width.$dirMod + ); + } + + /** + * 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 ezcImageImagemagickHandler::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 + * 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 scaleHeight( $height, $direction ) + { + if ( !is_int( $height ) || $height < 1 ) + { + throw new ezcBaseValueException( 'height', $height, 'int > 0' ); + } + $dirMod = $this->getDirectionModifier( $direction ); + $this->addFilterOption( + $this->getActiveReference(), + '-resize ', + 'x'.$height.$dirMod + ); + } + + /** + * 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 ezcImageImagemagickHandler::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 + * 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 scalePercent( $width, $height ) + { + if ( !is_int( $height ) || $height < 1 ) + { + throw new ezcBaseValueException( 'height', $height, 'int > 0' ); + } + if ( !is_int( $width ) || $width < 1 || $width > 100 ) + { + throw new ezcBaseValueException( 'width', $width, 'int > 0' ); + } + $this->addFilterOption( + $this->getActiveReference(), + '-resize', + $width.'%x'.$height.'%' + ); + } + + /** + * 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 ezcImageImagemagickHandler::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 + * 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 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->addFilterOption( + $this->getActiveReference(), + '-resize', + $width.'!x'.$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 ezcImageImagemagickHandler::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 + * 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 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' ); + } + + $data = getimagesize( $this->getActiveResource() ); + $x = ( $x >= 0 ) ? $x : $data[0] + $x; + $y = ( $y >= 0 ) ? $y : $data[1] + $y; + + $xStart = ( $xStart = min( $x, $x + $width ) ) >= 0 ? '+'.$xStart : $xStart; + $yStart = ( $yStart = min( $y, $y + $height ) ) >= 0 ? '+'.$yStart : $yStart; + $this->addFilterOption( + $this->getActiveReference(), + '-crop ', + abs( $width ).'x'.abs( $height ).$xStart.$yStart.'!' + ); + } + + /** + * Colorspace filter. + * Transform the color space of the picture. The following color space 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 ezcImageImagemagickHandler::applyFilter()} + * method, which enables you to specify the image a filter is applied to. + * + * @param int $space Colorspace, one of self::COLORSPACE_* constants. + * @return void + * + * @throws ezcImageInvalidReferenceException + * No loaded file could be found or an error destroyed a loaded reference + * @throws ezcBaseValueException + * If the parameter submitted as the colorspace was not within the + * self::COLORSPACE_* constants. + */ + public function colorspace( $space ) + { + switch ( $space ) + { + case self::COLORSPACE_GREY: + $this->addFilterOption( + $this->getActiveReference(), + '-colorspace', + 'GRAY' + ); + $this->addFilterOption( + $this->getActiveReference(), + '-colors', + '255' + ); + break; + case self::COLORSPACE_MONOCHROME: + $this->addFilterOption( + $this->getActiveReference(), + '-monochrome' + ); + break; + case self::COLORSPACE_SEPIA: + $this->addFilterOption( + $this->getActiveReference(), + '-sepia-tone', + '80%' + ); + break; + return; + default: + throw new ezcBaseValueException( 'space', $space, 'self::COLORSPACE_GREY, self::COLORSPACE_SEPIA, self::COLORSPACE_MONOCHROME' ); + break; + } + } + + /** + * Noise filter. + * Apply a noise transformation to the image. Valid values are the following + * strings: + * - 'Uniform' + * - 'Gaussian' + * - 'Multiplicative' + * - 'Impulse' + * - 'Laplacian' + * - 'Poisson' + * + * 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 strings $value Noise value as described above. + * @return void + * + * @throws ezcBaseValueException + * If the noise value is out of range. + * @throws ezcImageInvalidReferenceException + * No loaded file could be found or an error destroyed a loaded reference. + */ + public function noise( $value ) + { + $value = ucfirst( strtolower( $value ) ); + $possibleValues = array( + 'Uniform', + 'Gaussian', + 'Multiplicative', + 'Impulse', + 'Laplacian', + 'Poisson', + ); + if ( !in_array( $value, $possibleValues ) ) + { + throw new ezcBaseValueException( 'value', $value, 'Uniform, Gaussian, Multiplicative, Impulse, Laplacian, Poisson' ); + } + $this->addFilterOption( + $this->getActiveReference(), + '+noise', + $value + ); + } + + /** + * Swirl filter. + * Applies a swirl with the given intense to the image. + * + * 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 $value Intense of swirl. + * @return void + * + * @throws ezcImageInvalidReferenceException + * No loaded file could be found or an error destroyed a loaded reference. + * @throws ezcBaseValueException + * If the swirl value is out of range. + */ + public function swirl( $value ) + { + if ( !is_int( $value ) || $value < 0 ) + { + throw new ezcBaseValueException( 'value', $value, 'int >= 0' ); + } + $this->addFilterOption( + $this->getActiveReference(), + '-swirl', + $value + ); + } + + /** + * Border filter. + * Adds a border to the image. The width is measured in pixel. The color is + * defined in an array of hex values: + * + * + * 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 => , + * 1 => , + * 2 => , + * ); + * + * + * @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 ezcImageFilterFailedException + * If the operation performed by the the filter failed. + * @throws ezcBaseValueException + * If a submitted parameter was out of range or type. + */ + function border( $width, array $color ); +} +?> diff --git a/include/ezcomponents/ImageConversion/src/interfaces/geometry.php b/include/ezcomponents/ImageConversion/src/interfaces/geometry.php new file mode 100644 index 000000000..d065cd11c --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/interfaces/geometry.php @@ -0,0 +1,177 @@ + diff --git a/include/ezcomponents/ImageConversion/src/interfaces/handler.php b/include/ezcomponents/ImageConversion/src/interfaces/handler.php new file mode 100644 index 000000000..6a9bbbcd2 --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/interfaces/handler.php @@ -0,0 +1,280 @@ +mixed) + */ + protected $properties; + + /** + * Settings of the handlers + * + * @var ezcImageHandlerSettings + */ + protected $settings; + + /** + * 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. When overwriting + * the constructor. + * + * @param ezcImageHandlerSettings $settings + * Settings for the handler. + */ + public function __construct( ezcImageHandlerSettings $settings ) + { + $this->properties['name'] = $settings->referenceName; + $this->settings = $settings; + } + + /** + * Sets the property $name to $value. + * + * @throws ezcBasePropertyNotFoundException if the property does not exist. + * @throws ezcBasePropertyReadOnlyException if the property cannot be modified. + * @param string $name + * @param mixed $value + * @ignore + */ + public function __set( $name, $value ) + { + switch ( $name ) + { + case 'name': + 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 + * @return mixed + * @ignore + */ + public function __get( $name ) + { + switch ( $name ) + { + case 'name': + 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 'name': + return true; + default: + return false; + } + } + + /** + * Checks a file name for illegal characters. + * Checks if a file name contains illegal characters, which are ", ' and $. + * + * @param string $file The file name to check. + * @return void + * + * @throws ezcImageFileNameInvalidException + * If an invalid character (", ', $) is found in the file name. + */ + protected function checkFileName( $file ) + { + if ( strpos( $file, "'" ) !== false || strpos( $file, "'" ) !== false || strpos( $file, '$' ) !== false ) + { + throw new ezcImageFileNameInvalidException( $file ); + } + } + + /** + * Returns if a MIME conversion needs transparent color replacement. + * + * In case a transparency supporting MIME type (like image/png) is + * converted to one that does not support transparency, special steps need + * to be performed. This method returns if the given conversion from + * $inMime to $outMime is affected by this. + * + * @param string $inMime + * @param string $outMime + * @return bool + */ + protected function needsTransparencyConversion( $inMime, $outMime ) + { + $transparencyMimes = array( + 'image/gif' => true, + 'image/png' => true, + ); + return ( + $outMime !== null + && $inMime !== $outMime + && isset( $transparencyMimes[$inMime] ) + && !isset( $transparencyMimes[$outMime] ) + ); + } + + /** + * Load an image file. + * Loads an image file and returns a reference to it. + * + * For developers: The use of ezcImageHandler::loadCommon() is highly + * recommended for the implementation of this method! + * + * @param string $file File to load. + * @param string $mime The MIME type of the file. + * @return string Reference to the file in this handler. + */ + abstract public function load( $file, $mime = null ); + + /** + * Save an image file. + * Saves a given open file. Can optionally save to a new file name. + * The image reference is not freed automatically, so you need to call + * the close() method explicitly to free the referenced data. + * + * @see ezcImageHandler::load() + * @see ezcImageHandler::close() + * + * @param string $image File reference created through. + * @param string $newFile Filename to save the image to. + * @param string $mime New MIME type, if differs from + * initial one. + * @param ezcImageSaveOptions $options Options for saving. + * @return void + */ + abstract public function save( $image, $newFile = null, $mime = null, ezcImageSaveOptions $options = null ); + + /** + * Close the file referenced by $image. + * Frees the image reference. You should call close() before. + * + * @see ezcImageHandler::load() + * @see ezcImageHandler::save() + * @param string $reference The image reference. + * @return void + */ + abstract public function close( $reference ); + + /** + * Check wether a specific MIME type is allowed as input for this handler. + * + * @param string $mime MIME type to check if it's allowed. + * @return bool + */ + abstract public function allowsInput( $mime ); + + /** + * Checks wether a specific MIME type is allowed as output for this handler. + * + * @param string $mime MIME type to check if it's allowed. + * @return bool + */ + abstract public function allowsOutput( $mime ); + + /** + * Checks if a given filter is available in this handler. + * + * @param string $name Name of the filter to check for. + * @return bool + * + */ + abstract public function hasFilter( $name ); + + /** + * Returns a list of filters this handler provides. + * The list returned is in format: + * + * + * 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' => , + * 'mime' => , + * 'resource' => , + * ) + * ) + * + * @var array + */ + private $references = array(); + + /** + * Currently active image reference. + * This is used to determine by the filter, which image should be + * processed. + * + * @var string + */ + private $activeReference; + + /** + * 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. When overwriting + * the constructor. + * + * The contents of the $settings parameter may change from handler to + * handler. For detailed information take a look at the specific handler + * classes. + * + * @param ezcImageHandlerSettings $settings Settings for the handler. + */ + public function __construct( ezcImageHandlerSettings $settings ) + { + parent::__construct( $settings ); + } + + /** + * Destroyes the handler and closes all open references correctly. + * + * @return void + */ + public function __destruct() + { + foreach ( $this->references as $id => $data ) + { + $this->close( $id ); + } + } + + /** + * Check wether a specific MIME type is allowed as input for this handler. + * + * @param string $mime MIME type to check if it's allowed. + * @return bool + */ + public function allowsInput( $mime ) + { + return ( in_array( strtolower( $mime ), $this->inputTypes ) ); + } + + /** + * Checks wether a specific MIME type is allowed as output for this handler. + * + * @param string $mime MIME type to check if it's allowed. + * @return bool + */ + public function allowsOutput( $mime ) + { + return ( in_array( strtolower( $mime ), $this->outputTypes ) ); + } + + /** + * Checks if a given filter is available in this handler. + * + * @param string $name Name of the filter to check for. + * @return bool + * + */ + public function hasFilter( $name ) + { + return method_exists( $this, $name ); + } + + /** + * Returns a list of filters this handler provides. + * The list returned is in format: + * + * + * 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 => , + * 1 => , + * 2 => , + * ); + * + * + * If you are looking for a filter that just resizes your image to + * thumbnail size, you should consider the {@link + * ezcImageGeometryFilters::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() ); +} + +?> diff --git a/include/ezcomponents/ImageConversion/src/interfaces/watermark.php b/include/ezcomponents/ImageConversion/src/interfaces/watermark.php new file mode 100644 index 000000000..9ca810a36 --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/interfaces/watermark.php @@ -0,0 +1,74 @@ + diff --git a/include/ezcomponents/ImageConversion/src/options/save_options.php b/include/ezcomponents/ImageConversion/src/options/save_options.php new file mode 100644 index 000000000..ee822785c --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/options/save_options.php @@ -0,0 +1,84 @@ +save() methods. + * + * @property int $compression + * The compression level to use, if compression is supported by the + * target format (e.g. TIFF). A value between 0 and 9 (incl.) is + * expected. + * @property int $quality A quality indicator used to determine the quality of + * the target image, if supported by the target format (e.g. JPEG). A + * value between 0 and 100 (incl.) is expected. + * @property array(int) $transparencyReplacementColor + * Only certain image formats support transparent backgrounds (e.g. + * GIF and PNG). If such images are converted to a format that does + * not support transparency, this color will be used as the new + * background. The color value is given as an array of integers, each + * representing a color value in RGB between 0 and 255. + * 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 @@ + + *
  • {@link ezcImageGeometryFilters}
  • + *
  • {@link ezcImageColorspaceFilters}
  • + *
  • {@link ezcImageEffectFilters}
  • + *
  • {@link ezcImageWatermarkFilters}
  • + *
  • {@link ezcImageThumbnailFilters}
  • + * + * + * The options for each filter are represented by the parameters received by + * their corresponding method. You can determine if a certain {@link + * ezcImageHandler} implementation supports a filter by checking the interfaces + * this handler implements. + * + * @see ezcImageTransformation + * + * @package ImageConversion + * @version 1.3.5 + */ +class ezcImageFilter extends ezcBaseStruct +{ + /** + * Name of filter operation to use. + * + * @see ezcImageEffectFilters + * @see ezcImageGeometryFilters + * @see ezcImageColorspaceFilters + * + * @var string + */ + public $name; + + /** + * Associative array of options for the filter operation. + * The array key is the option name and the array entry is the value for + * the option. + * Consult each filter operation to see which names and values to use. + * + * @see ezcImageEffectFilters + * @see ezcImageGeometryFilters + * @see ezcImageColorspaceFilters + * + * @var array(string=>mixed) + */ + public $options; + + /** + * Initialize with the filter name and options. + * + * @see ezcImageFilter::$name + * @see ezcImageFilter::$options + * + * @param array $name Name of filter operation. + * @param array $options Associative array of options for filter operation. + */ + public function __construct( $name, array $options = array() ) + { + $this->name = $name; + $this->options = $options; + } +} +?> diff --git a/include/ezcomponents/ImageConversion/src/structs/handler_settings.php b/include/ezcomponents/ImageConversion/src/structs/handler_settings.php new file mode 100644 index 000000000..99faf305e --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/structs/handler_settings.php @@ -0,0 +1,87 @@ + + * array( + * => , + * [ => , ...] + * ) + * + * + * @var array + */ + public $options = array(); + + /** + * Initialize settings to be used by image handler. + * The settings passed as parameter will be read by the + * {@link ezcImageConverter converter} to figure out which image handler to + * use and then passed to the {@link ezcImageHandler image handler objects}. + * + * @see ezcImageHandlerSettings::$referenceName + * @see ezcImageHandlerSettings::$className + * @see ezcImageHandlerSettings::$settings + * + * @param string $referenceName + * The reference name for the handler, e.g. 'GD' or 'ImageMagick' + * @param string $className + * The name of the handler class to instantiate, e.g. + * 'ezcImageGdHandler' or 'ezcImageImagemagickHandler' + * @param array $options + * Associative array of settings for the handler. + */ + public function __construct( $referenceName, $className, array $options = array() ) + { + $this->referenceName = $referenceName; + $this->className = $className; + $this->options = $options; + } +} +?> diff --git a/include/ezcomponents/ImageConversion/src/transformation.php b/include/ezcomponents/ImageConversion/src/transformation.php new file mode 100644 index 000000000..aca1d798e --- /dev/null +++ b/include/ezcomponents/ImageConversion/src/transformation.php @@ -0,0 +1,401 @@ + + * $filters = array( + * new ezcImageFilter( 'scaleDownByWidth', + * array( + * 'width' => 100 + * ) + * ), + * new ezcImageFilter( 'crop', + * array( + * 'x' => 0, + * 'y' => 0, + * 'width' => 100, + * 'height' => 100, + * ) + * ), + * ); + * $mimeTypes = array( 'image/jpeg', 'image/png' ); + * + * // ezcImageTransformation object returned for further manipulation + * $thumbnail = $converter->createTransformation( + * 'thumbnail', + * $filters, + * $mimeTypes + * ); + * + * $converter->transform( 'thumbnail', 'var/storage/myOriginal1.jpg', + * 'var/storage/myThumbnail1' ); // res: image/jpeg + * $converter->transform( 'thumbnail', 'var/storage/myOriginal2.png', + * 'var/storage/myThumbnail2' ); // res: image/png + * $converter->transform( 'thumbnail', 'var/storage/myOriginal3.gif', + * 'var/storage/myThumbnail3' ); // res: image/.png + * + * // Animated GIF, will simply be copied! + * $converter->transform( 'thumbnail', 'var/storage/myOriginal4.gif', + * 'var/storage/myThumbnail4' ); // res: image/gif + * + * + * @see ezcImageConverter + * + * @package ImageConversion + * @version 1.3.5 + */ +class ezcImageTransformation +{ + /** + * Array of MIME types allowed as output for this transformation. + * Leave empty, for all MIME types to be allowed. + * + * @var array(string) + */ + protected $mimeOut; + + /** + * Stores the filters utilized by a transformation. + * + * @var array(ezcImageFilter) + */ + protected $filters; + + /** + * Stores the name of this transformation. + * + * @var string + */ + protected $name; + + /** + * The ezcImageConverter + * + * @var ezcImageConverter + */ + protected $converter; + + /** + * The handler last used for filtering. + * + * @var ezcImageHandler + */ + protected $lastHandler; + + /** + * Options for the final save step. + * + * @var ezcSaveOptions + */ + protected $saveOptions; + + /** + * Initialize transformation. + * + * @param ezcImageConverter $converter The global converter. + * @param string $name Name for the transformation. + * @param array(ezcImageFilter) $filters Filters to apply. + * @param array(string) $mimeOut Output MIME types. + * @param ezcImageSaveOptions $saveOptions Options for saving images. + * + * @throws ezcImageFiltersException + * On invalid filter or filter settings error. + * @throws ezcImageMimeTypeUnsupportedException + * If the output type is unsupported. + */ + public function __construct( ezcImageConverter $converter, $name, array $filters = array(), array $mimeOut = array(), ezcImageSaveOptions $saveOptions = null ) + { + $this->converter = $converter; + $this->name = $name; + $this->setFilters( $filters ); + $this->setMimeOut( $mimeOut ); + $this->setSaveOptions( $saveOptions !== null ? $saveOptions : new ezcImageSaveOptions() ); + } + + /** + * Add a filter to the conversion. + * Adds a filter with the specific settings. Filters can be added either + * before an existing filter or at the end (leave out $before parameter). + * + * @param ezcImageFilter $filter The filter definition. + * @param int $before Where to add the filter + * @return void + * + * @throws ezcImageFilterNotAvailableException + * If the given filter is not available. + */ + public function addFilter( ezcImageFilter $filter, $before = null ) + { + if ( $this->converter->hasFilter( $filter->name ) === false ) + { + throw new ezcImageFilterNotAvailableException( $filter->name ); + } + if ( isset( $before ) && isset( $this->filters[$before] ) ) + { + array_splice( $this->filters, $before, 0, array( $filter ) ); + return; + } + $this->filters[] = $filter; + } + + /** + * Determine output MIME type + * Returns the MIME type that the transformation will output. + * + * @param string $fileIn File that should deal as input for the transformation. + * @param string $mimeIn Specify the MIME type, so method does not need to. + * + * @return string MIME type the transformation will output. + * + * @throws ezcImageAnalyzerException If the input type is unsupported. + */ + public function getOutMime( $fileIn, $mimeIn = null ) + { + if ( !isset( $mimeIn ) ) + { + $analyzer = new ezcImageAnalyzer( $fileIn ); + $mimeIn = $analyzer->mime; + } + $mimeOut = $this->converter->getMimeOut( $mimeIn ); + // Is output type allowed by this transformation? Else use first allowed one... + return in_array( $mimeOut, $this->mimeOut ) ? $mimeOut : reset( $this->mimeOut ); + } + + /** + * Apply the given filters for the transformation. + * Applies the conversion as defined to the given file and saves it as + * defined. + * + * @param string $fileIn The file to transform. + * @param string $fileOut The file to save the transformed image to. + * @return void + * + * @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( $fileIn, $fileOut ) + { + // Sanity checks + if ( !is_file( $fileIn ) ) + { + throw new ezcBaseFileNotFoundException( $fileIn ); + } + if ( !is_readable( $fileIn ) ) + { + throw new ezcBaseFilePermissionException( $fileIn, ezcBaseFileException::READ ); + } + + // Start atomic file operation + $fileTmp = tempnam( dirname( $fileOut ) . DIRECTORY_SEPARATOR, '.'. basename( $fileOut ) ); + copy( $fileIn, $fileTmp ); + + try + { + // MIME types + $analyzer = new ezcImageAnalyzer( $fileTmp ); + + // Do not process animated GIFs + if ( $analyzer->data->isAnimated ) + { + copy( $fileTmp, $fileOut ); + unlink( $fileTmp ); + return; + } + + $mimeIn = $analyzer->mime; + } + catch ( ezcImageAnalyzerException $e ) + { + // Clean up + unlink( $fileTmp ); + // Rethrow + throw new ezcImageTransformationException( $e ); + } + + $outMime = $this->getOutMime( $fileTmp, $mimeIn ); + + $ref = ''; + + // Catch exceptions for cleanup + try + { + // Apply the filters + foreach ( $this->filters as $filter ) + { + // Avoid reopening in same handler + if ( isset( $this->lastHandler ) ) + { + if ( $this->lastHandler->hasFilter( $filter->name ) ) + { + $this->lastHandler->applyFilter( $ref, $filter ); + continue; + } + else + { + // Handler does not support filter, save file + $this->lastHandler->save( $ref ); + $this->lastHandler->close( $ref ); + } + } + // Get handler to perform filter correctly + $this->lastHandler = $this->converter->getHandler( $filter->name, $mimeIn ); + $ref = $this->lastHandler->load( $fileTmp, $mimeIn ); + $this->lastHandler->applyFilter( $ref, $filter ); + } + + // When no filters are performed by a transformation, we might have no last handler here + if ( !isset( $this->lastHandler ) ) + { + $this->lastHandler = $this->converter->getHandler( null, $mimeIn, $outMime ); + $ref = $this->lastHandler->load( $fileTmp, $mimeIn ); + } + + // Perform conversion + if ( $this->lastHandler->allowsOutput( ( $outMime ) ) ) + { + $this->lastHandler->convert( $ref, $outMime ); + } + else + { + // Close in last handler + $this->lastHandler->save( $ref ); + $this->lastHandler->close( $ref ); + // Destroy invalid reference (has been closed) + $ref = null; + // Retreive correct handler + $this->lastHandler = $this->converter->getHandler( null, $mimeIn, $outMime ); + // Load in new handler + $ref = $this->lastHandler->load( $fileTmp ); + // Perform conversion + $this->lastHandler->convert( $ref, $outMime ); + } + // Everything done, save and close + $this->lastHandler->save( $ref, null, null, $this->saveOptions ); + $this->lastHandler->close( $ref ); + } + catch ( ezcImageException $e ) + { + // Cleanup + if ( $ref !== null ) + { + $this->lastHandler->close( $ref ); + } + if ( file_exists( $fileTmp ) ) + { + unlink( $fileTmp ); + } + $this->lastHandler = null; + // Rethrow + throw new ezcImageTransformationException( $e ); + } + + // Cleanup + $this->lastHandler = null; + + // Finalize atomic file operation + if ( ezcBaseFeatures::os() === 'Windows' && file_exists( $fileOut ) ) + { + // Windows does not allows overwriting files using rename, + // therefore the file is unlinked here first. + if ( unlink( $fileOut ) === false ) + { + // Cleanup + unlink( $fileTmp ); + throw new ezcImageFileNotProcessableException( $fileOut, 'The file exists and could not be unlinked.' ); + } + } + if ( @rename( $fileTmp, $fileOut ) === false ) + { + unlink( $fileTmp ); + throw new ezcImageFileNotProcessableException( $fileOut, "The temporary file {$fileTmp} could not be renamed to {$fileOut}." ); + } + } + + /** + * Set the filters for this transformation. + * Checks if the filters defined are available and saves them to the created + * transformation if everything is okay. + * + * @param array(ezcImageFilter) $filters Array of {@link ezcImageFilter filter objects}. + * @return void + * + * @throws ezcImageFilterNotAvailableException + * If a filter is not available. + * @throws ezcBaseFileException + * If the filter array contains invalid object entries. + */ + protected function setFilters( array $filters ) + { + foreach ( $filters as $id => $filter ) + { + if ( !$filter instanceof ezcImageFilter ) + { + throw new ezcBaseSettingValueException( 'filters', 'array( int => ' . get_class( $filter ) . ' )', 'array( int => ezcImageFilter )' ); + } + if ( !$this->converter->hasFilter( $filter->name ) ) + { + throw new ezcImageFilterNotAvailableException( $filter->name ); + } + } + $this->filters = $filters; + } + + /** + * Sets the MIME types which are allowed for output. + * + * @param array $mime MIME types to allow output for. + * @return void + * + * @throws ezcImageMimeTypeUnsupportedException + * If the MIME types cannot be used as output of any of the + * handlers in the converter. + */ + protected function setMimeOut( array $mime ) + { + foreach ( $mime as $mimeType ) + { + if ( !$this->converter->allowsOutput( $mimeType ) ) + { + throw new ezcImageMimeTypeUnsupportedException( $mimeType, 'output' ); + } + } + $this->mimeOut = $mime; + } + + /** + * Sets the save options. + * Sets the save options, that are used for the final save step of the + * transformation. + * + * {@link ezcImageSaveOptions} + * + * @param ezcImageSaveOptions $options Save options. + * @return void + */ + public function setSaveOptions( ezcImageSaveOptions $options ) + { + $this->saveOptions = $options; + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/converter_test.php b/include/ezcomponents/ImageConversion/tests/converter_test.php new file mode 100644 index 000000000..7d98b6721 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/converter_test.php @@ -0,0 +1,614 @@ + "image/png", + "image/xpm" => "image/jpeg", + "image/wbmp" => "image/jpeg", + ); + if ( ezcBaseFeatures::os() === 'Windows' ) + { + unset( $conversionsIn["image/xpm"] ); + } + $settings = new ezcImageConverterSettings( + array( new ezcImageHandlerSettings( "GD", "ezcImageGdHandler" ) ), + $conversionsIn + ); + $this->converter = new ezcImageConverter( $settings ); + } + catch ( Exception $e ) + { + $this->markTestSkipped( $e->getMessage() ); + } + } + + protected function tearDown() + { + unset( $this->converter ); + } + + // Constructor tests + + public function testConstructSingleHandlerSuccess() + { + $conversionsIn = array( + "image/gif" => "image/png", + "image/xpm" => "image/jpeg", + "image/wbmp" => "image/jpeg", + ); + if ( ezcBaseFeatures::os() === 'Windows' ) + { + unset( $conversionsIn["image/xpm"] ); + } + try + { + $settings = new ezcImageConverterSettings( array( new ezcImageHandlerSettings( "GD", "ezcImageGdHandler" ) ), + $conversionsIn ); + $converter = new ezcImageConverter( $settings ); + } + catch ( Exception $e ) + { + $this->markTestSkipped(); + } + + $handlers = $this->readAttribute( $converter, "handlers" ); + $settings = $this->readAttribute( $converter, "settings" ); + + $this->assertType( + "ezcImageGdHandler", + $handlers["GD"], + "Handler is not an instance of ezcImageGdHandler." + ); + $this->assertEquals( + $conversionsIn, + $settings->conversions, + "Conversions not registered successfully." + ); + } + + public function testConstructFailureInvalidSettings() + { + $conversionsIn = array( + "image/gif" => "image/png", + "image/xpm" => "image/jpeg", + "image/wbmp" => "image/jpeg", + ); + if ( ezcBaseFeatures::os() === 'Windows' ) + { + unset( $conversionsIn["image/xpm"] ); + } + try + { + $settings = new ezcImageConverterSettings( + array( new stdClass() ), + $conversionsIn + ); + $converter = new ezcImageConverter( $settings ); + $this->fail( 'Exception not thrown on invalid handler settings.' ); + } + catch ( ezcImageHandlerSettingsInvalidException $e ) + {} + } + + public function testConstructSingleHandlerFailureOutputMimeTypeNotSupported() + { + $conversionsIn = array( + "image/gif" => "image/png", + "image/xpm" => "application/ezc", + "image/wbmp" => "image/jpeg", + ); + $settings = new ezcImageConverterSettings( + array( new ezcImageHandlerSettings( "GD", "ezcImageGdHandler" ) ), + $conversionsIn + ); + try + { + $converter = new ezcImageConverter( $settings ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Expected excption not thrown when creating ezcImageConverter with unsupported conversion." ); + } + + public function testConstructSingleHandlerFailureInputMimeTypeNotSupported() + { + $conversionsIn = array( + "image/gif" => "image/png", + "image/ezc" => "image/jpeg", + "image/wbmp" => "image/jpeg", + ); + $settings = new ezcImageConverterSettings( + array( new ezcImageHandlerSettings( "GD", "ezcImageGdHandler" ) ), + $conversionsIn + ); + + try + { + $converter = new ezcImageConverter( $settings ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Expected excption not thrown when creating ezcImageConverter with unsupported conversion." ); + } + + public function testConstructSingleHandlerFailureHandlerNotAvailable() + { + $conversionsIn = array( + "image/gif" => "image/png", + "image/xpm" => "image/jpeg", + "image/wbmp" => "image/jpeg", + ); + $settings = new ezcImageConverterSettings( + array( new ezcImageHandlerSettings( "Toby", "fooImageHandlerToby" ) ), + $conversionsIn + ); + try + { + $converter = new ezcImageConverter( $settings ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + return; + } + $this->fail( "Expected excption not thrown when creating ezcImageConverter with unsupported handler." ); + } + + // Transformation tests + + public function testCreateTransformation() + { + $transformation = $this->converter->createTransformation( "thumbnail", array(), array() ); + $this->assertType( + "ezcImageTransformation", + $transformation, + "Converter does not return created transformation." + ); + } + + // Issue #12667: ezcImageConverter doesn't pass saveOptions to + // ezcImageTransformation.' + public function testCreateTransformationWithSaveOptions() + { + $options = new ezcImageSaveOptions(); + $transformation = $this->converter->createTransformation( "thumbnail", array(), array(), $options ); + $this->assertAttributeSame( + $options, + 'saveOptions', + $transformation, + "Converter did not pass save options correctly." + ); + } + + // MIME type tests + + public function testAllowsInputSuccess() + { + $this->assertTrue( + $this->converter->allowsInput( "image/jpeg" ), + "Converter does not allow input MIME type . This sounds impossible..." + ); + } + + public function testAllowsInputFailure() + { + $this->assertFalse( + $this->converter->allowsInput( "application/ezc" ), + "Converter allows input MIME type . This sounds impossible..." + ); + } + + public function testAllowsOutputSuccess() + { + $this->assertTrue( + $this->converter->allowsOutput( "image/jpeg" ), + "Converter does not allow output MIME type . This sounds impossible..." + ); + } + + public function testAllowsOutputFailure() + { + $this->assertFalse( + $this->converter->allowsOutput( "application/ezc" ), + "Converter allows output MIME type . This sounds impossible..." + ); + } + + public function testGetMimeOutSuccessConversionPerformed() + { + $this->assertEquals( + "image/png", + $this->converter->getMimeOut( "image/gif" ), + "Converter converted MIME type incorrectly." + ); + } + + public function testGetMimeOutSuccessNoConversionPerformed() + { + $this->assertEquals( + "image/jpeg", + $this->converter->getMimeOut( "image/jpeg" ), + "Converter converted MIME type incorrectly." + ); + } + + public function testGetMimeOutFailure() + { + try + { + $this->converter->getMimeOut( "application/ezc" ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Expected exception not thrown when getting output MIME type for invalid input type." ); + } + + // Filter tests + + public function testHasFilterSuccess() + { + $this->assertTrue( + $this->converter->hasFilter( "scale" ), + "Converter does not have filter . This sounds impossible..." + ); + } + + public function testHasFilterFailure() + { + $this->assertFalse( + $this->converter->hasFilter( "ezc" ), + "Converter has filter . This sounds impossible..." + ); + } + + public function testGetFilterNamesIncluded() + { + $standardFilters = array( + "scale", + "scaleWidth", + "scaleHeight", + "scalePercent", + "scaleExact", + "crop", + "colorspace", + ); + $this->assertEquals( + array_intersect( $standardFilters, $this->converter->getFilterNames() ), + $standardFilters, + "Converter seems not to support standard filters from GD." + ); + } + + public function testGetFilterNamesExcluded() + { + $impossibleFilters = array( + "__construct", + "__destruct", + "__get", + "__set", + "__call", + ); + $this->assertEquals( + array_intersect( $impossibleFilters, $this->converter->getFilterNames() ), + array(), + "Converter seems to support impossible filters." + ); + } + + // Conversion tests + + public function testApplyFilterSuccessScale() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + + $this->converter->applyFilter( + new ezcImageFilter( + "scale", + array( "width" => 10, "height" => 10, "direction" => ezcImageGeometryFilters::SCALE_DOWN ) + ), + $srcPath, + $dstPath + ); + + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Image comparison failed.", + 2000 + ); + } + + public function testApplyFilterSuccessColorspace() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + + $this->converter->applyFilter( new ezcImageFilter( "colorspace", array( "space" => ezcImageColorspaceFilters::COLORSPACE_MONOCHROME ) ), + $srcPath, $dstPath ); + + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Image comparison failed.", + 2000 + ); + } + + public function testApplyFilterSuccessColorspaceDefinedHandler() + { + $srcPath = $this->testFiles['jpeg']; + $dstPath = $this->getTempPath(); + + + $this->converter->applyFilter( + new ezcImageFilter( + 'colorspace', + array( + 'space' => ezcImageColorspaceFilters::COLORSPACE_MONOCHROME + ) + ), + $srcPath, + $dstPath, + 'GD' + ); + + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Image comparison failed.", + 2000 + ); + } + + public function testApplyFilterFailureHandlerNotAvailable() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + try + { + $this->converter->applyFilter( + new ezcImageFilter( "colorspace", array( "space" => ezcImageColorspaceFilters::COLORSPACE_MONOCHROME ) ), + $srcPath, + $dstPath, + "ezc" + ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + return; + } + $this->fail( "Converter did not throw exception on not available handler while applying filter." ); + } + + public function testApplyFilterFailurewFilterNotAvailable() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + try + { + $this->converter->applyFilter( + new ezcImageFilter( "ezc", array() ), + $srcPath, + $dstPath + ); + } + catch ( ezcImageFilterNotAvailableException $e ) + { + return; + } + $this->fail( "Converter did not throw exception on not available filter while applying filter." ); + } + + // Handler retrieval tests + + public function testGetHandlerSuccessNoFilterNoInNoOut() + { + $this->assertType( + "ezcImageHandler", + $this->converter->getHandler(), + "Returned object is not an ezcImageHandler." + ); + } + + public function testGetHandlerSuccessFilterNoInNoOut() + { + $this->assertType( + "ezcImageHandler", + $this->converter->getHandler( "scale" ), + "Returned object is not an ezcImageHandler." + ); + } + + public function testGetHandlerSuccessNoFilterInNoOut() + { + $this->assertType( + "ezcImageHandler", + $this->converter->getHandler( null, "image/jpeg" ), + "Returned object is not an ezcImageHandler." + ); + } + + public function testGetHandlerSuccessNoFilterNoInOut() + { + $this->assertType( + "ezcImageHandler", + $this->converter->getHandler( null, null, "image/jpeg" ), + "Returned object is not an ezcImageHandler." + ); + } + + public function testGetHandlerSuccessFilterInOut() + { + $this->assertType( + "ezcImageHandler", + $this->converter->getHandler( "scale", "image/jpeg", "image/jpeg" ), + "Returned object is not an ezcImageHandler." + ); + } + + public function testGetHandlerFailureFilterNoInNoOut() + { + try + { + $this->converter->getHandler( "ezc" ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + return; + } + $this->fail( "Converter did not throw exception on request of impossible handler." ); + } + + public function testGetHandlerFailureNoFilterInNoOut() + { + try + { + $this->converter->getHandler( null, "application/ezc" ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + return; + } + $this->fail( "Converter did not throw exception on request of impossible handler." ); + } + + public function testGetHandlerFailureNoFilterNoInOut() + { + try + { + $this->converter->getHandler( null, null, "application/ezc" ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + return; + } + $this->fail( "Converter did not throw exception on request of impossible handler." ); + } + + public function testGetHandlerFailureNotAvailableFilterInOut() + { + try + { + $this->converter->getHandler( "ezc", "application/ezc", "application/ezc" ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + return; + } + $this->fail( "Converter did not throw exception on request of impossible handler." ); + } + + public function testCreateTransformationFailureCreatedTwice() + { + $this->converter->createTransformation( 'foo', array(), array() ); + + try + { + $this->converter->createTransformation( 'foo', array(), array() ); + $this->fail( 'Expected not thrown on double created transformation.' ); + } + catch ( ezcImageTransformationAlreadyExistsException $e ) + {} + } + + public function testRemoveTransformationSuccess() + { + $this->converter->createTransformation( 'foo', array(), array() ); + $transformations = $this->readAttribute( $this->converter, "transformations" ); + + $this->assertEquals( + 1, + count( $transformations ) + ); + + $this->converter->removeTransformation( 'foo' ); + + $transformations = $this->readAttribute( $this->converter, "transformations" ); + $this->assertEquals( + 0, + count( $transformations ) + ); + } + + public function testRemoveTransformationFailureNotExists() + { + try + { + $this->converter->removeTransformation( 'foo' ); + $this->fail( 'Expected not thrown on remove of non-existent transformation.' ); + } + catch ( ezcImageTransformationNotAvailableException $e ) + {} + } + + public function testApplyTransformationSuccess() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $this->converter->createTransformation( + 'foo', + array( + new ezcImageFilter( + "colorspace", + array( + "space" => ezcImageColorspaceFilters::COLORSPACE_MONOCHROME + ) + ), + ), + array( 'image/jpeg' ) + ); + $this->converter->transform( 'foo', $srcPath, $dstPath ); + } + + public function testApplyTransformationFailureNonExistent() + { + try + { + $this->converter->transform( 'foo', '', '' ); + $this->fail( 'Expected not thrown when non-existent transformation should be applied.' ); + } + catch ( ezcImageTransformationNotAvailableException $e ) + {} + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessColorspace b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessColorspace new file mode 100644 index 000000000..55396532a Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessColorspace differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessColorspaceDefinedHandler b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessColorspaceDefinedHandler new file mode 100644 index 000000000..55396532a Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessColorspaceDefinedHandler differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessScale new file mode 100644 index 000000000..94484d83e Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionConverterTest_testApplyFilterSuccessScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceGrey b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceGrey new file mode 100644 index 000000000..e3fc3d432 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceGrey differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceMonochrome b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceMonochrome new file mode 100644 index 000000000..55396532a Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceMonochrome differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceSepia b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceSepia new file mode 100644 index 000000000..10a72e60e Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testColorspaceSepia differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_1 new file mode 100644 index 000000000..0cc35357c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_2 new file mode 100644 index 000000000..33da13888 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_3 new file mode 100644 index 000000000..0cc35357c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropNegativeOffset_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropThumbnailHorizontal b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropThumbnailHorizontal new file mode 100644 index 000000000..75154aac5 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropThumbnailHorizontal differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropThumbnailVertical b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropThumbnailVertical new file mode 100644 index 000000000..beab50ac6 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropThumbnailVertical differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropTransparent b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropTransparent new file mode 100644 index 000000000..8df4dad3f Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCropTransparent differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_0_Offset b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_0_Offset new file mode 100644 index 000000000..add7a8270 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_0_Offset differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_1 new file mode 100644 index 000000000..620944a1c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_2 new file mode 100644 index 000000000..620944a1c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_3 new file mode 100644 index 000000000..2d09593df Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_4 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_4 new file mode 100644 index 000000000..a8fda0e13 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testCrop_4 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailHorizontal b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailHorizontal new file mode 100644 index 000000000..3aebe9f50 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailHorizontal differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailTooLargeColorArray b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailTooLargeColorArray new file mode 100644 index 000000000..3aebe9f50 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailTooLargeColorArray differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailVertical b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailVertical new file mode 100644 index 000000000..e336896fb Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testFillThumbnailVertical differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testScaleTransparent b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testScaleTransparent new file mode 100644 index 000000000..37fd75500 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testScaleTransparent differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkAbsoluteNoScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkAbsoluteNoScale new file mode 100644 index 000000000..9f82dba8a Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkAbsoluteNoScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkAbsoluteScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkAbsoluteScale new file mode 100644 index 000000000..a8295073c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkAbsoluteScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkPercentNoScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkPercentNoScale new file mode 100644 index 000000000..a5a841599 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkPercentNoScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkPercentScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkPercentScale new file mode 100644 index 000000000..cee39326d Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersGdTest_testWatermarkPercentScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testBorder_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testBorder_2 new file mode 100644 index 000000000..9492fe9ba Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testBorder_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testBorder_5 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testBorder_5 new file mode 100644 index 000000000..107d6af53 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testBorder_5 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceGrey b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceGrey new file mode 100644 index 000000000..417892fe4 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceGrey differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceMonochrome b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceMonochrome new file mode 100644 index 000000000..e667a184b Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceMonochrome differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceSepia b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceSepia new file mode 100644 index 000000000..3e8034eb4 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testColorspaceSepia differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_1 new file mode 100644 index 000000000..dc08d3abf Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_2 new file mode 100644 index 000000000..999e3daa7 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_3 new file mode 100644 index 000000000..dc08d3abf Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropNegativeOffset_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropThumbnailHorizontal b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropThumbnailHorizontal new file mode 100644 index 000000000..13c3d77d9 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropThumbnailHorizontal differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropThumbnailVertical b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropThumbnailVertical new file mode 100644 index 000000000..fe876ae0d Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCropThumbnailVertical differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_0_Offset b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_0_Offset new file mode 100644 index 000000000..47eef4656 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_0_Offset differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_1 new file mode 100644 index 000000000..a8f9d06d7 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_2 new file mode 100644 index 000000000..a8f9d06d7 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_3 new file mode 100644 index 000000000..a5195fb41 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testCrop_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailHorizontal b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailHorizontal new file mode 100644 index 000000000..d0dd813bc Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailHorizontal differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailTooLargeColorArray b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailTooLargeColorArray new file mode 100644 index 000000000..d0dd813bc Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailTooLargeColorArray differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailVertical b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailVertical new file mode 100644 index 000000000..227dfd899 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testFillThumbnailVertical differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseGaussian b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseGaussian new file mode 100644 index 000000000..b0bdf4d24 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseGaussian differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseImpulse b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseImpulse new file mode 100644 index 000000000..015a70e71 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseImpulse differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseLaplacian b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseLaplacian new file mode 100644 index 000000000..0eb01554b Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseLaplacian differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseMultiplicative b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseMultiplicative new file mode 100644 index 000000000..2c9dd2325 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseMultiplicative differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoisePoisson b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoisePoisson new file mode 100644 index 000000000..8c9613bd2 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoisePoisson differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseUniform b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseUniform new file mode 100644 index 000000000..5db872429 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testNoiseUniform differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScale new file mode 100644 index 000000000..e46230630 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleDown_do b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleDown_do new file mode 100644 index 000000000..4fb93b72f Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleDown_do differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleDown_dont b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleDown_dont new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleDown_dont differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_1 new file mode 100644 index 000000000..5ec2793da Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_2 new file mode 100644 index 000000000..a967c6c7f Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_3 new file mode 100644 index 000000000..6919a0019 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleExact_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightDown_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightDown_1 new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightDown_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightDown_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightDown_2 new file mode 100644 index 000000000..13493f440 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightDown_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightUp_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightUp_1 new file mode 100644 index 000000000..c193fb33a Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightUp_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightUp_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightUp_2 new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleHeightUp_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScalePercent_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScalePercent_1 new file mode 100644 index 000000000..840bfda39 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScalePercent_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScalePercent_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScalePercent_2 new file mode 100644 index 000000000..5ec2793da Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScalePercent_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleUp_do b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleUp_do new file mode 100644 index 000000000..e46230630 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleUp_do differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleUp_dont b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleUp_dont new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleUp_dont differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthBoth b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthBoth new file mode 100644 index 000000000..4fc9657e0 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthBoth differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthDown_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthDown_1 new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthDown_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthDown_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthDown_2 new file mode 100644 index 000000000..4fc9657e0 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthDown_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthUp_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthUp_1 new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthUp_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthUp_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthUp_2 new file mode 100644 index 000000000..c42a97284 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testScaleWidthUp_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_10 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_10 new file mode 100644 index 000000000..16949bd3c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_10 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_100 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_100 new file mode 100644 index 000000000..7b78e4aae Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_100 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_50 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_50 new file mode 100644 index 000000000..5c2f8d48d Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testSwirl_50 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkAbsoluteNoScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkAbsoluteNoScale new file mode 100644 index 000000000..75357f890 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkAbsoluteNoScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkAbsoluteScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkAbsoluteScale new file mode 100644 index 000000000..c769b46a6 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkAbsoluteScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkPercentNoScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkPercentNoScale new file mode 100644 index 000000000..4c2514fe4 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkPercentNoScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkPercentScale b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkPercentScale new file mode 100644 index 000000000..00d65e44d Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionFiltersShellTest_testWatermarkPercentScale differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testApplyFilterMultiple b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testApplyFilterMultiple new file mode 100644 index 000000000..afa0dd6f6 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testApplyFilterMultiple differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testApplyFilterSingle b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testApplyFilterSingle new file mode 100644 index 000000000..ae2dbbff7 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testApplyFilterSingle differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testConvertTransparentNonTransparent b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testConvertTransparentNonTransparent new file mode 100644 index 000000000..ecae43c51 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testConvertTransparentNonTransparent differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveNewfileConvert b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveNewfileConvert new file mode 100644 index 000000000..3105761d6 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveNewfileConvert differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveNewfileNoconvert b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveNewfileNoconvert new file mode 100644 index 000000000..11cc69d8c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveNewfileNoconvert differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveOldfileNoconvert b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveOldfileNoconvert new file mode 100644 index 000000000..11cc69d8c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerGdTest_testSaveOldfileNoconvert differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testApplyFilterMultiple b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testApplyFilterMultiple new file mode 100644 index 000000000..3e0abc5d3 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testApplyFilterMultiple differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testApplyFilterSingle b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testApplyFilterSingle new file mode 100644 index 000000000..07698fc30 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testApplyFilterSingle differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testConvertTransparentNonTransparent b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testConvertTransparentNonTransparent new file mode 100644 index 000000000..a0b8f3192 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerShellTest_testConvertTransparentNonTransparent differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveIllegalFileNameFailure b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveIllegalFileNameFailure new file mode 100644 index 000000000..7d44d2a0b Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveIllegalFileNameFailure differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveNewfileConvert b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveNewfileConvert new file mode 100644 index 000000000..7d44d2a0b Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveNewfileConvert differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveNewfileNoconvert b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveNewfileNoconvert new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveNewfileNoconvert differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveOldfileNoconvert b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveOldfileNoconvert new file mode 100644 index 000000000..a9daff945 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionHandlerTest_testSaveOldfileNoconvert differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testMultiTransformjpeg b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testMultiTransformjpeg new file mode 100644 index 000000000..1c380920d Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testMultiTransformjpeg differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testMultiTransformpng b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testMultiTransformpng new file mode 100644 index 000000000..1b7755cdc Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testMultiTransformpng differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGifAnimated b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGifAnimated new file mode 100644 index 000000000..4f59d06c0 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGifAnimated differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_1 new file mode 100644 index 000000000..e35a89377 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_2 new file mode 100644 index 000000000..f10fa3442 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_3 new file mode 100644 index 000000000..fd1edbede Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessGif_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_1 new file mode 100644 index 000000000..1c380920d Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_2 new file mode 100644 index 000000000..2d43dc785 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_3 new file mode 100644 index 000000000..5922e936b Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessJpeg_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_1 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_1 new file mode 100644 index 000000000..ec5471099 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_1 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_2 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_2 new file mode 100644 index 000000000..eba713f00 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_2 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_3 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_3 new file mode 100644 index 000000000..aba95a0ee Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_3 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_4 b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_4 new file mode 100644 index 000000000..e0651b9cd Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformSuccessPng_4 differ diff --git a/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformationChangingHandlersForFilters b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformationChangingHandlersForFilters new file mode 100644 index 000000000..f5f35dda8 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/compare/ezcImageConversionTransformationTest_testTransformationChangingHandlersForFilters differ diff --git a/include/ezcomponents/ImageConversion/tests/data/gif_animated.gif b/include/ezcomponents/ImageConversion/tests/data/gif_animated.gif new file mode 100644 index 000000000..4f59d06c0 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/gif_animated.gif differ diff --git a/include/ezcomponents/ImageConversion/tests/data/gif_nonanimated.gif b/include/ezcomponents/ImageConversion/tests/data/gif_nonanimated.gif new file mode 100644 index 000000000..511c33aa2 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/gif_nonanimated.gif differ diff --git a/include/ezcomponents/ImageConversion/tests/data/jpeg.jpg b/include/ezcomponents/ImageConversion/tests/data/jpeg.jpg new file mode 100644 index 000000000..1646b523c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/jpeg.jpg differ diff --git a/include/ezcomponents/ImageConversion/tests/data/png.png b/include/ezcomponents/ImageConversion/tests/data/png.png new file mode 100644 index 000000000..f70805c1e Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/png.png differ diff --git a/include/ezcomponents/ImageConversion/tests/data/png_transparent.png b/include/ezcomponents/ImageConversion/tests/data/png_transparent.png new file mode 100644 index 000000000..9edc8499c Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/png_transparent.png differ diff --git a/include/ezcomponents/ImageConversion/tests/data/text.txt b/include/ezcomponents/ImageConversion/tests/data/text.txt new file mode 100644 index 000000000..bb2863ca5 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/data/text.txt @@ -0,0 +1 @@ +Litte test file, which is no valid image... diff --git a/include/ezcomponents/ImageConversion/tests/data/watermark.png b/include/ezcomponents/ImageConversion/tests/data/watermark.png new file mode 100644 index 000000000..797a0d9c4 Binary files /dev/null and b/include/ezcomponents/ImageConversion/tests/data/watermark.png differ diff --git a/include/ezcomponents/ImageConversion/tests/data/xpm.xpm b/include/ezcomponents/ImageConversion/tests/data/xpm.xpm new file mode 100644 index 000000000..07bb71f62 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/data/xpm.xpm @@ -0,0 +1,5318 @@ +/* XPM */ +static char * xpm_xpm[] = { +"250 160 5155 2", +" c #FFFFFF", +". c #8585FF", +"+ c #8687FF", +"@ c #8689FF", +"# c #868AFF", +"$ c #868BFF", +"% c #878DFF", +"& c #878EFF", +"* c #8790FF", +"= c #8792FF", +"- c #8893FF", +"; c #8895FF", +"> c #8996FF", +", c #8997FF", +"' c #8999FF", +") c #8A9BFF", +"! c #8A9CFF", +"~ c #8A9EFF", +"{ c #8A9FFF", +"] c #8BA1FF", +"^ c #8BA2FF", +"/ c #8BA3FF", +"( c #8CA4FF", +"_ c #8CA6FF", +": c #8CA7FF", +"< c #8CA9FF", +"[ c #8CAAFF", +"} c #8DABFF", +"| c #8EADFF", +"1 c #8EAFFF", +"2 c #8EB0FF", +"3 c #8EB1FF", +"4 c #8EB3FF", +"5 c #8FB4FF", +"6 c #8FB6FF", +"7 c #8FB7FF", +"8 c #90B8FF", +"9 c #90BAFF", +"0 c #91BBFF", +"a c #91BDFF", +"b c #92BDFF", +"c c #92BFFF", +"d c #92C0FF", +"e c #92C1FF", +"f c #92C3FF", +"g c #93C4FF", +"h c #94C5FF", +"i c #94C7FF", +"j c #94C8FF", +"k c #94C9FF", +"l c #94CAFF", +"m c #8586FF", +"n c #8688FF", +"o c #868CFF", +"p c #878FFF", +"q c #8791FF", +"r c #8892FF", +"s c #8995FF", +"t c #8998FF", +"u c #899AFF", +"v c #8A9DFF", +"w c #8BA0FF", +"x c #8AA1FF", +"y c #8BA4FF", +"z c #8CA5FF", +"A c #8DA8FF", +"B c #8DA9FF", +"C c #8EACFF", +"D c #8DAEFF", +"E c #8FB1FF", +"F c #8FB2FF", +"G c #8FB5FF", +"H c #90B6FF", +"I c #90B7FF", +"J c #91B9FF", +"K c #91BAFF", +"L c #91BCFF", +"M c #91BEFF", +"N c #92C4FF", +"O c #93C5FF", +"P c #93C6FF", +"Q c #95CBFF", +"R c #94CCFF", +"S c #96CEFF", +"T c #878BFF", +"U c #868EFF", +"V c #8894FF", +"W c #8896FF", +"X c #8B9FFF", +"Y c #8CA8FF", +"Z c #8CABFF", +"` c #8DACFF", +" . c #8EAEFF", +".. c #8EB2FF", +"+. c #8FB3FF", +"@. c #90B9FF", +"#. c #91BFFF", +"$. c #93C3FF", +"%. c #95CDFF", +"&. c #95CEFF", +"*. c #96CFFF", +"=. c #96D0FF", +"-. c #8587FF", +";. c #878CFF", +">. c #8897FF", +",. c #899DFF", +"'. c #90BCFF", +"). c #92BEFF", +"!. c #92C2FF", +"~. c #95CAFF", +"{. c #95CCFF", +"]. c #95CFFF", +"^. c #97D1FF", +"/. c #96D3FF", +"(. c #97D4FF", +"_. c #4658A8", +":. c #3E56A4", +"<. c #3755A0", +"[. c #2F549C", +"}. c #275399", +"|. c #205295", +"1. c #1E5695", +"2. c #1E5C95", +"3. c #1E6095", +"4. c #1D6594", +"5. c #1D6A94", +"6. c #1C7094", +"7. c #1C7494", +"8. c #1C7993", +"9. c #1B7E93", +"0. c #1B8393", +"a. c #1A8893", +"b. c #198E92", +"c. c #1A9292", +"d. c #199792", +"e. c #189C92", +"f. c #17A192", +"g. c #17A692", +"h. c #17AE91", +"i. c #16BA91", +"j. c #15C491", +"k. c #13D091", +"l. c #12DB8F", +"m. c #11E68F", +"n. c #11F18F", +"o. c #0FFC8F", +"p. c #16FF8F", +"q. c #21FE8E", +"r. c #2AFD8E", +"s. c #34FC8E", +"t. c #3DFC8D", +"u. c #47FB8D", +"v. c #51FC8C", +"w. c #5AFA8D", +"x. c #65FA8C", +"y. c #6FFA8C", +"z. c #78F98B", +"A. c #82F88C", +"B. c #8CF88B", +"C. c #95F78B", +"D. c #9FF78A", +"E. c #A8F78A", +"F. c #B2F68A", +"G. c #BCF68A", +"H. c #C6F589", +"I. c #D0F489", +"J. c #DAF489", +"K. c #E3F388", +"L. c #ECF389", +"M. c #F6F288", +"N. c #F8EF8B", +"O. c #F7EB8F", +"P. c #F8E892", +"Q. c #F8E595", +"R. c #F8E199", +"S. c #F9DE9C", +"T. c #F8DAA0", +"U. c #F9D8A3", +"V. c #FAD3A6", +"W. c #FAD1AA", +"X. c #FACDAD", +"Y. c #FACAB2", +"Z. c #FAC6B5", +"`. c #FAC3B8", +" + c #FABFBC", +".+ c #FBBCBF", +"++ c #FBB8C3", +"@+ c #FCB4C6", +"#+ c #FCAFCC", +"$+ c #FCAAD1", +"%+ c #FDA5D6", +"&+ c #FDA1DA", +"*+ c #FD9CDF", +"=+ c #FD97E4", +"-+ c #FE92E9", +";+ c #FE8EEE", +">+ c #FF89F3", +",+ c #FF84F8", +"'+ c #FE7FFC", +")+ c #FA7BFF", +"!+ c #F077FF", +"~+ c #E774FF", +"{+ c #DF70FF", +"]+ c #D56CFF", +"^+ c #CC69FF", +"/+ c #C365FF", +"(+ c #BA61FF", +"_+ c #B05EFF", +":+ c #A75AFF", +"<+ c #9E57FF", +"[+ c #804BFF", +"}+ c #6140FF", +"|+ c #4333FF", +"1+ c #3D31FF", +"2+ c #3C31FF", +"3+ c #868DFF", +"4+ c #8CA3FF", +"5+ c #8DAAFF", +"6+ c #90B5FF", +"7+ c #96D1FF", +"8+ c #96D2FF", +"9+ c #97D5FF", +"0+ c #97D7FF", +"a+ c #515AAE", +"b+ c #4959A9", +"c+ c #4157A6", +"d+ c #3A56A2", +"e+ c #32559E", +"f+ c #2A549A", +"g+ c #235297", +"h+ c #1F5495", +"i+ c #1F5A95", +"j+ c #1E5F94", +"k+ c #1E6394", +"l+ c #1D6894", +"m+ c #1D6D93", +"n+ c #1C7293", +"o+ c #1B7894", +"p+ c #1B7C93", +"q+ c #1B8193", +"r+ c #1B8793", +"s+ c #198B93", +"t+ c #199093", +"u+ c #199592", +"v+ c #189A92", +"w+ c #18A092", +"x+ c #17A491", +"y+ c #17AA91", +"z+ c #16B592", +"A+ c #14C091", +"B+ c #14CC90", +"C+ c #13D790", +"D+ c #11E18F", +"E+ c #10EC8F", +"F+ c #0FF88F", +"G+ c #13FE8E", +"H+ c #1CFE8E", +"I+ c #26FE8E", +"J+ c #30FD8E", +"K+ c #3AFD8D", +"L+ c #43FC8D", +"M+ c #4DFC8D", +"N+ c #57FB8D", +"O+ c #61FA8C", +"P+ c #6AFA8C", +"Q+ c #74F98B", +"R+ c #7EF98B", +"S+ c #87F98B", +"T+ c #91F78B", +"U+ c #9BF88B", +"V+ c #A5F78A", +"W+ c #AEF68A", +"X+ c #B8F689", +"Y+ c #C2F589", +"Z+ c #CCF489", +"`+ c #D6F389", +" @ c #DFF389", +".@ c #E8F388", +"+@ c #F3F288", +"@@ c #F7F08A", +"#@ c #F8ED8D", +"$@ c #F7E991", +"%@ c #F8E694", +"&@ c #F9E397", +"*@ c #F8E09B", +"=@ c #F9DB9F", +"-@ c #F9D8A2", +";@ c #F9D5A6", +">@ c #F9D2A9", +",@ c #FACFAD", +"'@ c #FACBB0", +")@ c #FAC7B3", +"!@ c #FAC4B7", +"~@ c #FAC1BA", +"{@ c #FBBEBD", +"]@ c #FBBAC1", +"^@ c #FBB6C5", +"/@ c #FCB2CA", +"(@ c #FCADCE", +"_@ c #FCA8D4", +":@ c #FDA3D9", +"<@ c #FC9EDD", +"[@ c #FD9AE3", +"}@ c #FE94E8", +"|@ c #FE8FEC", +"1@ c #FE8BF1", +"2@ c #FF86F6", +"3@ c #FF81FB", +"4@ c #FE7DFF", +"5@ c #F478FF", +"6@ c #EB75FF", +"7@ c #E271FF", +"8@ c #D96EFF", +"9@ c #D06AFF", +"0@ c #C666FF", +"a@ c #BD63FF", +"b@ c #B45FFF", +"c@ c #AB5CFF", +"d@ c #A258FF", +"e@ c #8B50FF", +"f@ c #6E44FF", +"g@ c #4F39FF", +"h@ c #3C30FF", +"i@ c #8890FF", +"j@ c #8793FF", +"k@ c #8898FF", +"l@ c #8B9EFF", +"m@ c #8CA2FF", +"n@ c #93C2FF", +"o@ c #96D4FF", +"p@ c #97D6FF", +"q@ c #98D7FF", +"r@ c #98D9FF", +"s@ c #99DAFF", +"t@ c #5C5CB3", +"u@ c #545BAF", +"v@ c #4D59AC", +"w@ c #4458A7", +"x@ c #3D56A3", +"y@ c #35559F", +"z@ c #2D539C", +"A@ c #255297", +"B@ c #1F5294", +"C@ c #1E5795", +"D@ c #1F5D95", +"E@ c #1E6294", +"F@ c #1D6694", +"G@ c #1D6C94", +"H@ c #1C7093", +"I@ c #1C7593", +"J@ c #1B7A93", +"K@ c #1B7F94", +"L@ c #1B8593", +"M@ c #1A8992", +"N@ c #1A8E93", +"O@ c #199393", +"P@ c #199892", +"Q@ c #189D92", +"R@ c #18A291", +"S@ c #17A792", +"T@ c #16B191", +"U@ c #15BC91", +"V@ c #15C791", +"W@ c #13D290", +"X@ c #12DD90", +"Y@ c #12E88F", +"Z@ c #10F38F", +"`@ c #0FFE8F", +" # c #19FE8E", +".# c #22FE8E", +"+# c #2CFD8E", +"@# c #36FD8D", +"## c #40FD8D", +"$# c #49FC8D", +"%# c #53FC8D", +"&# c #5DFA8C", +"*# c #67FA8C", +"=# c #70F98B", +"-# c #7AF98B", +";# c #84F98B", +"># c #8EF88B", +",# c #97F88A", +"'# c #A1F78A", +")# c #AAF68A", +"!# c #B4F58A", +"~# c #BEF58A", +"{# c #C8F489", +"]# c #D1F489", +"^# c #DBF389", +"/# c #E4F389", +"(# c #EFF389", +"_# c #F8F288", +":# c #F7EE8B", +"<# c #F8EA90", +"[# c #F8E793", +"}# c #F8E496", +"|# c #F9E09A", +"1# c #F9DD9D", +"2# c #F9D9A1", +"3# c #F9D6A4", +"4# c #F9D3A7", +"5# c #F9CFAB", +"6# c #FACCAF", +"7# c #FAC9B2", +"8# c #FAC6B6", +"9# c #FAC2B9", +"0# c #FBBEBC", +"a# c #FBBBC0", +"b# c #FCB3C8", +"c# c #FCAECD", +"d# c #FDA4D6", +"e# c #FDA0DC", +"f# c #FD9BE0", +"g# c #FD96E5", +"h# c #FE91EA", +"i# c #FE8DEF", +"j# c #FE87F4", +"k# c #FF83F9", +"l# c #FF7EFD", +"m# c #F87AFF", +"n# c #EF77FF", +"o# c #E673FF", +"p# c #DD70FF", +"q# c #D36BFF", +"r# c #CA68FF", +"s# c #C165FF", +"t# c #B761FF", +"u# c #AE5DFF", +"v# c #A55AFF", +"w# c #9754FF", +"x# c #7949FF", +"y# c #5B3DFF", +"z# c #8589FF", +"A# c #8891FF", +"B# c #899BFF", +"C# c #8AA0FF", +"D# c #8BA6FF", +"E# c #93C7FF", +"F# c #95C9FF", +"G# c #94CBFF", +"H# c #97D2FF", +"I# c #98D6FF", +"J# c #99DCFF", +"K# c #99DDFF", +"L# c #665EB8", +"M# c #5F5CB5", +"N# c #575BB1", +"O# c #4F5AAD", +"P# c #4758A9", +"Q# c #4056A5", +"R# c #3855A1", +"S# c #31549D", +"T# c #29539A", +"U# c #215296", +"V# c #1F5595", +"W# c #1E5B95", +"X# c #1E6594", +"Y# c #1D6994", +"Z# c #1C6E93", +"`# c #1C7893", +" $ c #1B7D93", +".$ c #1B8293", +"+$ c #1A8793", +"@$ c #1A8C92", +"#$ c #199193", +"$$ c #199693", +"%$ c #199B92", +"&$ c #18A592", +"*$ c #17AC92", +"=$ c #16B891", +"-$ c #15C391", +";$ c #14CD90", +">$ c #13D990", +",$ c #12E490", +"'$ c #11EF8F", +")$ c #10FA8F", +"!$ c #15FF8E", +"~$ c #1FFE8E", +"{$ c #28FE8E", +"]$ c #32FD8E", +"^$ c #3CFC8D", +"/$ c #46FC8D", +"($ c #4FFC8D", +"_$ c #59FB8C", +":$ c #63FA8C", +"<$ c #6CFA8B", +"[$ c #76F98B", +"}$ c #80F98B", +"|$ c #8AF88B", +"1$ c #93F78B", +"2$ c #9DF78B", +"3$ c #A7F68A", +"4$ c #B1F689", +"5$ c #BAF58A", +"6$ c #C4F589", +"7$ c #CEF489", +"8$ c #D8F489", +"9$ c #E1F389", +"0$ c #EBF289", +"a$ c #F4F288", +"b$ c #F8F08A", +"c$ c #F7EC8E", +"d$ c #F7E992", +"e$ c #F8E695", +"f$ c #F8E298", +"g$ c #F9DF9C", +"h$ c #F9DBA0", +"i$ c #F9D1AA", +"j$ c #FACEAD", +"k$ c #FACAB0", +"l$ c #FAC6B4", +"m$ c #FAC3B7", +"n$ c #FBC0BB", +"o$ c #FBBDBE", +"p$ c #FBB9C2", +"q$ c #FBB5C6", +"r$ c #FCB1CB", +"s$ c #FCABD0", +"t$ c #FCA7D5", +"u$ c #FCA2D9", +"v$ c #FD9DDF", +"w$ c #FE98E4", +"x$ c #FD94E8", +"y$ c #FE8FED", +"z$ c #FE8AF2", +"A$ c #FE85F7", +"B$ c #FF80FC", +"C$ c #FB7CFF", +"D$ c #F378FF", +"E$ c #E974FF", +"F$ c #E071FF", +"G$ c #D76DFF", +"H$ c #CE6AFF", +"I$ c #C466FF", +"J$ c #BB62FF", +"K$ c #B25EFF", +"L$ c #A95BFF", +"M$ c #A057FF", +"N$ c #854EFF", +"O$ c #6741FF", +"P$ c #4836FF", +"Q$ c #8AA2FF", +"R$ c #97D3FF", +"S$ c #98DAFF", +"T$ c #99DBFF", +"U$ c #9ADDFF", +"V$ c #99DEFF", +"W$ c #9ADFFF", +"X$ c #715FBE", +"Y$ c #6A5EBA", +"Z$ c #625CB6", +"`$ c #5A5BB3", +" % c #525AAE", +".% c #4B59AA", +"+% c #4357A6", +"@% c #3B56A3", +"#% c #34559F", +"$% c #2C549B", +"%% c #245297", +"&% c #1F5994", +"*% c #1E5E95", +"=% c #1E6794", +"-% c #1C6C93", +";% c #1D7293", +">% c #1C7693", +",% c #1B7C94", +"'% c #1B8093", +")% c #1A8593", +"!% c #1A8A93", +"~% c #199492", +"{% c #189992", +"]% c #199E92", +"^% c #18A392", +"/% c #17A892", +"(% c #16B391", +"_% c #16BE91", +":% c #14C991", +"<% c #14D490", +"[% c #12DF8F", +"}% c #12EB8F", +"|% c #10F68E", +"1% c #11FF8E", +"2% c #1BFE8E", +"3% c #24FE8E", +"4% c #2EFD8E", +"5% c #38FC8D", +"6% c #41FC8D", +"7% c #4BFC8D", +"8% c #55FB8C", +"9% c #5FFA8C", +"0% c #69FA8C", +"a% c #72F98C", +"b% c #7CF98C", +"c% c #86F88B", +"d% c #8FF88B", +"e% c #99F88A", +"f% c #A3F68B", +"g% c #ADF68A", +"h% c #B6F68A", +"i% c #C0F58A", +"j% c #CAF489", +"k% c #D3F489", +"l% c #DDF488", +"m% c #E7F388", +"n% c #F0F288", +"o% c #F8F089", +"p% c #F7ED8C", +"q% c #F7EA90", +"r% c #F8E794", +"s% c #F8E497", +"t% c #F9DF9B", +"u% c #F9DC9E", +"v% c #F9D3A8", +"w% c #FACFAC", +"x% c #F9CBAF", +"y% c #FAC8B3", +"z% c #FAC4B6", +"A% c #FBC1B9", +"B% c #FBBAC0", +"C% c #FBB7C4", +"D% c #FBB2C9", +"E% c #FCAECE", +"F% c #FCA8D3", +"G% c #FDA3D7", +"H% c #FD9FDC", +"I% c #FD9AE1", +"J% c #FE95E7", +"K% c #FE91EB", +"L% c #FE8BF0", +"M% c #FE86F5", +"N% c #FE82FA", +"O% c #FF7DFF", +"P% c #F679FF", +"Q% c #ED76FF", +"R% c #E472FF", +"S% c #DB6FFF", +"T% c #D16BFF", +"U% c #C867FF", +"V% c #BF64FF", +"W% c #B660FF", +"X% c #AC5DFF", +"Y% c #A459FF", +"Z% c #9152FF", +"`% c #7346FF", +" & c #553AFF", +".& c #8DADFF", +"+& c #90BBFF", +"@& c #98D8FF", +"#& c #9AE0FF", +"$& c #9AE1FF", +"%& c #9BE3FF", +"&& c #7C61C4", +"*& c #7560BF", +"=& c #6C5EBC", +"-& c #645DB8", +";& c #5D5CB4", +">& c #565AB0", +",& c #4E59AC", +"'& c #3E57A4", +")& c #3756A0", +"!& c #2E559D", +"~& c #275299", +"{& c #1F5194", +"]& c #1F5794", +"^& c #1E5B94", +"/& c #1E6094", +"(& c #1D6F94", +"_& c #1B7993", +":& c #1B7E94", +"<& c #1A8393", +"[& c #198D93", +"}& c #199392", +"|& c #18A192", +"1& c #18A692", +"2& c #16AE91", +"3& c #15BA91", +"4& c #14C490", +"5& c #14D090", +"6& c #13DB90", +"7& c #12E68F", +"8& c #17FF8E", +"9& c #20FE8D", +"0& c #2AFE8E", +"a& c #33FD8E", +"b& c #3EFC8D", +"c& c #47FC8D", +"d& c #51FB8C", +"e& c #5BFB8C", +"f& c #64FB8C", +"g& c #82F98B", +"h& c #8BF88A", +"i& c #96F78B", +"j& c #9EF78B", +"k& c #BCF58A", +"l& c #C6F58A", +"m& c #CFF489", +"n& c #DAF389", +"o& c #E3F389", +"p& c #EDF388", +"q& c #F7EE8C", +"r& c #F8EB8F", +"s& c #F9E595", +"t& c #F8DB9F", +"u& c #F9D7A3", +"v& c #F9D4A7", +"w& c #FACDAE", +"x& c #FAC9B1", +"y& c #FBC6B5", +"z& c #FCB4C7", +"A& c #FCA6D6", +"B& c #FCA1DB", +"C& c #FD9CE0", +"D& c #FE97E4", +"E& c #FD8EEE", +"F& c #FE89F3", +"G& c #FE84F8", +"H& c #FF7FFD", +"I& c #DE70FF", +"J& c #D56DFF", +"K& c #CB69FF", +"L& c #C265FF", +"M& c #B962FF", +"N& c #A75BFF", +"O& c #7F4BFF", +"P& c #613FFF", +"Q& c #9ADEFF", +"R& c #9AE2FF", +"S& c #9BE4FF", +"T& c #9CE5FF", +"U& c #8763C9", +"V& c #7F61C5", +"W& c #7860C1", +"X& c #705FBE", +"Y& c #685DB9", +"Z& c #605CB5", +"`& c #595CB2", +" * c #5159AD", +".* c #4958A9", +"+* c #4257A6", +"@* c #3956A2", +"#* c #2A539A", +"$* c #225296", +"%* c #1F5594", +"&* c #1F5A94", +"** c #1D5F94", +"=* c #1E6395", +"-* c #1D6D94", +";* c #1C7294", +">* c #1B7793", +",* c #1C7C93", +"'* c #1A8693", +")* c #1A8B93", +"!* c #199092", +"~* c #199692", +"{* c #189F92", +"]* c #18A492", +"^* c #17AB92", +"/* c #16B591", +"(* c #15C091", +"_* c #13D690", +":* c #12E290", +"<* c #11ED8F", +"[* c #10F88F", +"}* c #13FF8E", +"|* c #27FE8E", +"1* c #30FE8D", +"2* c #3AFC8D", +"3* c #44FC8D", +"4* c #57FB8C", +"5* c #61FB8C", +"6* c #6AFA8B", +"7* c #74FA8B", +"8* c #88F88B", +"9* c #9BF78A", +"0* c #A5F68A", +"a* c #C2F58A", +"b* c #CCF48A", +"c* c #D6F489", +"d* c #E9F388", +"e* c #F7EC8D", +"f* c #F8E990", +"g* c #F8DF9B", +"h* c #F9DB9E", +"i* c #FAD1A9", +"j* c #FACEAC", +"k* c #FBC7B4", +"l* c #FBC0BA", +"m* c #FCB1CA", +"n* c #FCADCF", +"o* c #FDA8D4", +"p* c #FDA2D9", +"q* c #FD9EDD", +"r* c #FD99E3", +"s* c #FE8AF1", +"t* c #FD7CFF", +"u* c #F479FF", +"v* c #CF6BFF", +"w* c #C667FF", +"x* c #AA5CFF", +"y* c #A158FF", +"z* c #6D44FF", +"A* c #4E37FF", +"B* c #8DA7FF", +"C* c #9CE4FF", +"D* c #9CE6FF", +"E* c #9CE7FF", +"F* c #9DE7FF", +"G* c #9365CF", +"H* c #8B64CA", +"I* c #8262C6", +"J* c #7B61C3", +"K* c #7360BE", +"L* c #6B5EBB", +"M* c #635DB7", +"N* c #535AAF", +"O* c #4C59AC", +"P* c #4558A8", +"Q* c #3D57A4", +"R* c #35569F", +"S* c #2D549C", +"T* c #255397", +"U* c #1F5295", +"V* c #1E5794", +"W* c #1E5C94", +"X* c #1D6294", +"Y* c #1D6794", +"Z* c #1D6B94", +"`* c #1C7194", +" = c #1B7F93", +".= c #1A8493", +"+= c #1A8993", +"@= c #198E93", +"#= c #17B191", +"$= c #14C790", +"%= c #13DD8F", +"&= c #11E98F", +"*= c #0FFE8E", +"== c #18FE8E", +"-= c #23FD8E", +";= c #2CFE8D", +">= c #35FD8E", +",= c #3FFD8D", +"'= c #49FC8C", +")= c #53FB8C", +"!= c #70FA8B", +"~= c #AAF78A", +"{= c #B4F689", +"]= c #BEF589", +"^= c #C7F58A", +"/= c #D2F489", +"(= c #DCF389", +"_= c #E5F389", +":= c #EFF288", +"<= c #F7F289", +"[= c #F8E396", +"}= c #F9DAA0", +"|= c #F9D0AB", +"1= c #FACDAF", +"2= c #FBBFBC", +"3= c #FABCC0", +"4= c #FBB7C3", +"5= c #FCB4C8", +"6= c #FCAFCD", +"7= c #FCAAD2", +"8= c #FCA4D6", +"9= c #FDA0DB", +"0= c #FD9AE0", +"a= c #FD96E6", +"b= c #FF88F4", +"c= c #FF82F9", +"d= c #FF7EFE", +"e= c #F87BFF", +"f= c #E572FF", +"g= c #DC6FFF", +"h= c #C064FF", +"i= c #AF5EFF", +"j= c #9755FF", +"k= c #7948FF", +"l= c #8A9AFF", +"m= c #8FB8FF", +"n= c #94C6FF", +"o= c #93C8FF", +"p= c #96CDFF", +"q= c #9BE1FF", +"r= c #9CE8FF", +"s= c #9DEAFF", +"t= c #9E67D5", +"u= c #9666D0", +"v= c #8D64CC", +"w= c #8562C8", +"x= c #7D61C4", +"y= c #7660C1", +"z= c #6E5FBC", +"A= c #665DB8", +"B= c #5F5DB4", +"C= c #575AB1", +"D= c #4F5AAC", +"E= c #4858A9", +"F= c #3856A1", +"G= c #30549D", +"H= c #205296", +"I= c #1F5B95", +"J= c #1E6494", +"K= c #1C6F93", +"L= c #1B7893", +"M= c #1B8893", +"N= c #1A9192", +"O= c #189B92", +"P= c #17AD92", +"Q= c #14C391", +"R= c #13CE90", +"S= c #11E48F", +"T= c #10EF8F", +"U= c #10FA8E", +"V= c #15FE8E", +"W= c #29FD8E", +"X= c #32FD8D", +"Y= c #3BFC8D", +"Z= c #45FC8D", +"`= c #4FFB8D", +" - c #59FA8C", +".- c #62FB8C", +"+- c #6CFA8C", +"@- c #7FF98C", +"#- c #94F88B", +"$- c #A6F78A", +"%- c #B0F58A", +"&- c #BAF589", +"*- c #CDF489", +"=- c #D8F389", +"-- c #EAF388", +";- c #F8EC8E", +">- c #F8E992", +",- c #F8E594", +"'- c #F8E198", +")- c #F9DE9B", +"!- c #F9D4A6", +"~- c #F9D0AA", +"{- c #FAC7B4", +"]- c #FCB0CB", +"^- c #FD98E3", +"/- c #FD93E8", +"(- c #FE8EED", +"_- c #FE8AF3", +":- c #FF85F7", +"<- c #FE80FC", +"[- c #FC7CFF", +"}- c #D76EFF", +"|- c #BB63FF", +"1- c #B25FFF", +"2- c #9F58FF", +"3- c #8FB0FF", +"4- c #90B4FF", +"5- c #91C0FF", +"6- c #95D0FF", +"7- c #98D5FF", +"8- c #9BE2FF", +"9- c #9BE5FF", +"0- c #9DE9FF", +"a- c #9EEBFF", +"b- c #9DECFF", +"c- c #9EEDFF", +"d- c #A968DA", +"e- c #A267D6", +"f- c #9966D2", +"g- c #9164CE", +"h- c #8963CA", +"i- c #8162C6", +"j- c #7960C2", +"k- c #725FBE", +"l- c #5A5CB2", +"m- c #4A59AB", +"n- c #33549F", +"o- c #1E5895", +"p- c #1E5E94", +"q- c #1D6394", +"r- c #1D7194", +"s- c #1B7B93", +"t- c #198F93", +"u- c #199493", +"v- c #199992", +"w- c #189E91", +"x- c #15BE91", +"y- c #14CA90", +"z- c #13D490", +"A- c #11EB8F", +"B- c #11F68F", +"C- c #1BFF8F", +"D- c #25FE8E", +"E- c #38FC8E", +"F- c #42FC8D", +"G- c #4CFC8C", +"H- c #68FA8B", +"I- c #72FA8C", +"J- c #7CF98B", +"K- c #85F88B", +"L- c #90F88B", +"M- c #99F78B", +"N- c #A3F78B", +"O- c #ACF78A", +"P- c #B7F58A", +"Q- c #D3F389", +"R- c #DDF389", +"S- c #F1F288", +"T- c #F8EE8D", +"U- c #F8E693", +"V- c #F8E397", +"W- c #F8DD9E", +"X- c #F9D6A5", +"Y- c #F9D2A8", +"Z- c #FACFAB", +"`- c #FACBAF", +" ; c #FAC5B6", +".; c #FBC2BA", +"+; c #FCB2C9", +"@; c #FDA8D3", +"#; c #FCA4D8", +"$; c #FE9AE2", +"%; c #FD95E6", +"&; c #FD90EB", +"*; c #FF87F5", +"=; c #FE81FA", +"-; c #E372FF", +";; c #DA6FFF", +">; c #C868FF", +",; c #BF63FF", +"'; c #B661FF", +"); c #AD5DFF", +"!; c #99D9FF", +"~; c #98DBFF", +"{; c #9DE8FF", +"]; c #9DEBFF", +"^; c #9EEEFF", +"/; c #9EEFFF", +"(; c #9FF0FF", +"_; c #B46BE0", +":; c #AC69DC", +"<; c #A467D7", +"[; c #9C66D3", +"}; c #9465CF", +"|; c #8C64CB", +"1; c #8462C7", +"2; c #745FBF", +"3; c #6D5FBC", +"4; c #655DB8", +"5; c #555AB0", +"6; c #4658A9", +"7; c #3F57A4", +"8; c #2F559C", +"9; c #1F5795", +"0; c #1E6194", +"a; c #1E6694", +"b; c #1C6A94", +"c; c #1B7994", +"d; c #1A8D92", +"e; c #199292", +"f; c #16AE92", +"g; c #15C591", +"h; c #10F18F", +"i; c #10FD8E", +"j; c #17FE8E", +"k; c #34FD8D", +"l; c #3EFD8D", +"m; c #48FC8D", +"n; c #52FB8C", +"o; c #6EF98B", +"p; c #78F98C", +"q; c #82F88B", +"r; c #96F88A", +"s; c #9FF78B", +"t; c #A9F68B", +"u; c #B2F58A", +"v; c #F9E596", +"w; c #F9D7A4", +"x; c #FAD0AA", +"y; c #FBBFBB", +"z; c #FBAFCC", +"A; c #FCA5D6", +"B; c #FCA1DA", +"C; c #FD93E9", +"D; c #FE83F8", +"E; c #F97BFF", +"F; c #F177FF", +"G; c #C266FF", +"H; c #B961FF", +"I; c #9BE6FF", +"J; c #9DE6FF", +"K; c #9EECFF", +"L; c #9FEFFF", +"M; c #9EF1FF", +"N; c #A0F1FF", +"O; c #A0F3FF", +"P; c #C06CE6", +"Q; c #B86BE1", +"R; c #B069DD", +"S; c #A868D9", +"T; c #9F67D6", +"U; c #9766D1", +"V; c #8F64CD", +"W; c #7760C1", +"X; c #705FBD", +"Y; c #685EBA", +"Z; c #585BB1", +"`; c #4958AA", +" > c #4258A6", +".> c #2A539B", +"+> c #225297", +"@> c #1E5F95", +"#> c #1C6D94", +"$> c #1C7793", +"%> c #1A8792", +"&> c #189692", +"*> c #199A92", +"=> c #15C191", +"-> c #12E18F", +";> c #12FF8E", +">> c #4DFC8C", +",> c #7DF98B", +"'> c #92F78B", +")> c #9CF78A", +"!> c #A5F78B", +"~> c #AFF68A", +"{> c #B8F58A", +"]> c #E9F389", +"^> c #F8DB9E", +"/> c #F9D4A5", +"(> c #FAD2A9", +"_> c #FACAAF", +":> c #FBC1BA", +"<> c #FBB9C1", +"[> c #FCACCF", +"}> c #FDA2D8", +"|> c #FD9EDE", +"1> c #FD99E2", +"2> c #FE94E7", +"3> c #FE90EC", +"4> c #FF8BF1", +"5> c #FE86F6", +"6> c #FE7CFF", +"7> c #E171FF", +"8> c #D86EFF", +"9> c #CF6AFF", +"0> c #C766FF", +"a> c #93C1FF", +"b> c #99DFFF", +"c> c #9CE9FF", +"d> c #9EEAFF", +"e> c #9FF1FF", +"f> c #9FF2FF", +"g> c #9FF3FF", +"h> c #A0F4FF", +"i> c #A0F5FF", +"j> c #CB6EEB", +"k> c #C46CE7", +"l> c #BC6CE3", +"m> c #B36ADE", +"n> c #AB69DB", +"o> c #A367D6", +"p> c #9B66D2", +"q> c #9264CE", +"r> c #8A63CB", +"s> c #7B61C2", +"t> c #725FBF", +"u> c #6B5EBA", +"v> c #3D57A3", +"w> c #3555A0", +"x> c #255398", +"y> c #1F5394", +"z> c #1C6C94", +"A> c #1B7A94", +"B> c #1B8493", +"C> c #18A792", +"D> c #16BC91", +"E> c #12E890", +"F> c #11F48F", +"G> c #0FFF8E", +"H> c #19FE8F", +"I> c #23FE8E", +"J> c #2CFE8E", +"K> c #36FC8E", +"L> c #3FFC8D", +"M> c #53FB8D", +"N> c #5DFB8C", +"O> c #66FA8C", +"P> c #70FA8C", +"Q> c #8DF88B", +"R> c #97F88B", +"S> c #A1F78B", +"T> c #ABF68A", +"U> c #B5F589", +"V> c #DBF488", +"W> c #E6F388", +"X> c #EEF288", +"Y> c #F7F188", +"Z> c #F7EA8F", +"`> c #F9DAA1", +" , c #FAD3A8", +"., c #FAC5B5", +"+, c #FBB3C8", +"@, c #FBAECD", +"#, c #FDA5D7", +"$, c #FD9BE1", +"%, c #FD91EA", +"&, c #FE8DF0", +"*, c #FE88F4", +"=, c #FE7EFE", +"-, c #EF76FF", +";, c #E573FF", +">, c #DD6FFF", +",, c #9BE0FF", +"', c #A1F6FF", +"), c #A1F8FF", +"!, c #D770F1", +"~, c #CF6FED", +"{, c #C76EE9", +"], c #BE6CE4", +"^, c #B66BE1", +"/, c #AE69DD", +"(, c #A668D8", +"_, c #9E66D5", +":, c #9665D0", +"<, c #8E64CC", +"[, c #8662C8", +"}, c #7E62C5", +"|, c #765FC0", +"1, c #6E5EBC", +"2, c #665DB9", +"3, c #5F5CB4", +"4, c #4859A8", +"5, c #4057A5", +"6, c #285399", +"7, c #1E5A95", +"8, c #1C6E94", +"9, c #1C7493", +"0, c #1C7994", +"a, c #1A8C93", +"b, c #1A9193", +"c, c #199B93", +"d, c #18A591", +"e, c #15B791", +"f, c #14CE90", +"g, c #13D98F", +"h, c #0FFA8E", +"i, c #28FD8E", +"j, c #3CFC8E", +"k, c #46FC8C", +"l, c #6CF98B", +"m, c #76F98C", +"n, c #80F88B", +"o, c #94F78A", +"p, c #9DF78A", +"q, c #A7F68B", +"r, c #B1F68A", +"s, c #BBF68A", +"t, c #D7F489", +"u, c #E1F489", +"v, c #F5F288", +"w, c #F8E991", +"x, c #F9D7A2", +"y, c #FAD4A6", +"z, c #F9CAB0", +"A, c #FBC4B8", +"B, c #FBBCBE", +"C, c #FBB4C6", +"D, c #FBB0CB", +"E, c #FCA6D4", +"F, c #FC9CDF", +"G, c #FD98E4", +"H, c #FE93E9", +"I, c #FD8FED", +"J, c #FE89F2", +"K, c #FE84F7", +"L, c #FC7BFF", +"M, c #F278FF", +"N, c #DF71FF", +"O, c #A1F7FF", +"P, c #A2F9FF", +"Q, c #A2FBFF", +"R, c #E272F7", +"S, c #DA71F2", +"T, c #D270EF", +"U, c #CA6DEA", +"V, c #C26CE7", +"W, c #B96BE2", +"X, c #B26ADE", +"Y, c #A969DB", +"Z, c #A167D5", +"`, c #9064CE", +" ' c #8863C9", +".' c #8062C5", +"+' c #695EBA", +"@' c #615CB6", +"#' c #5A5BB2", +"$' c #4B58AA", +"%' c #4357A7", +"&' c #1E5894", +"*' c #1E5D94", +"=' c #1D6395", +"-' c #1C7193", +";' c #1B8693", +">' c #1A8A92", +",' c #189E92", +"'' c #17B491", +")' c #14CA91", +"!' c #12E08F", +"~' c #10F68F", +"{' c #1AFE8E", +"]' c #38FD8E", +"^' c #55FB8D", +"/' c #5FFB8C", +"(' c #72FA8B", +"_' c #7CF88B", +":' c #90F78B", +"<' c #99F78A", +"[' c #A3F68A", +"}' c #ACF68A", +"|' c #C0F589", +"1' c #CAF589", +"2' c #DDF489", +"3' c #E7F389", +"4' c #F7F189", +"5' c #FAD2A8", +"6' c #FAC8B2", +"7' c #FAC1B9", +"8' c #FABEBD", +"9' c #FBBBC1", +"0' c #FCA8D2", +"a' c #FDA4D7", +"b' c #FD9FDD", +"c' c #FD9AE2", +"d' c #FE90EB", +"e' c #FE8CF0", +"f' c #FF82FA", +"g' c #F67AFF", +"h' c #9AE3FF", +"i' c #9DEDFF", +"j' c #A0F2FF", +"k' c #A1F5FF", +"l' c #A0F6FF", +"m' c #A2FAFF", +"n' c #A3FCFF", +"o' c #EB73FB", +"p' c #E672F8", +"q' c #DD71F4", +"r' c #D570F1", +"s' c #CD6EEC", +"t' c #C56DE8", +"u' c #BD6CE4", +"v' c #B46AE0", +"w' c #AD69DC", +"x' c #9465D0", +"y' c #8362C7", +"z' c #7C61C3", +"A' c #755FC0", +"B' c #5D5CB3", +"C' c #3655A0", +"D' c #2E549C", +"E' c #265399", +"F' c #1F5695", +"G' c #1D6A93", +"H' c #1D7093", +"I' c #1C7594", +"J' c #1B8992", +"K' c #199793", +"L' c #199D92", +"M' c #17AF91", +"N' c #15C590", +"O' c #12E78F", +"P' c #10F18E", +"Q' c #10FC8F", +"R' c #2BFD8E", +"S' c #34FD8E", +"T' c #48FB8C", +"U' c #95F88B", +"V' c #A9F68A", +"W' c #BCF689", +"X' c #EDF288", +"Y' c #F7F288", +"Z' c #F8EE8B", +"`' c #F9DE9D", +" ) c #FACCAD", +".) c #FAC2B8", +"+) c #FAC0BC", +"@) c #FBB8C2", +"#) c #FD97E5", +"$) c #FE8DEE", +"%) c #FF83F8", +"&) c #FE7FFD", +"*) c #F97AFF", +"=) c #FF0000", +"-) c #CE5673", +";) c #95D1FF", +">) c #D26873", +",) c #A1F9FF", +"') c #A3FBFF", +")) c #A2FCFF", +"!) c #A3FDFF", +"~) c #A3FEFF", +"{) c #A4FFFF", +"]) c #EC72F8", +"^) c #EB73FA", +"/) c #E973F9", +"() c #E172F6", +"_) c #D970F2", +":) c #D06FED", +"<) c #C86EEA", +"[) c #B86AE1", +"}) c #B069DE", +"|) c #9765D1", +"1) c #8663C8", +"2) c #7F62C5", +"3) c #6F5EBD", +"4) c #685EB9", +"5) c #605CB6", +"6) c #595BB2", +"7) c #505AAE", +"8) c #4959AA", +"9) c #1D6494", +"0) c #983842", +"a) c #1A8193", +"b) c #1A9092", +"c) c #17A492", +"d) c #17AB91", +"e) c #12D790", +"f) c #12E28F", +"g) c #1DFE8E", +"h) c #30FD8D", +"i) c #58FB8C", +"j) c #91F88B", +"k) c #9CF78B", +"l) c #B9F58A", +"m) c #D5F389", +"n) c #E0F389", +"o) c #F8E398", +"p) c #F9DC9F", +"q) c #F9D5A5", +"r) c #F9CEAC", +"s) c #FBC4B6", +"t) c #FABDBE", +"u) c #FCA7D4", +"v) c #FE99E2", +"w) c #FF7373", +"x) c #CE5773", +"y) c #CF5A73", +"z) c #CF5B73", +"A) c #CF5C73", +"B) c #B590B3", +"C) c #D16173", +"D) c #D16273", +"E) c #B79AB3", +"F) c #D36973", +"G) c #9EF0FF", +"H) c #A2F8FF", +"I) c #A2FDFF", +"J) c #A3FFFF", +"K) c #A4FFFE", +"L) c #A5FFFD", +"M) c #FFB3B3", +"N) c #EC70F6", +"O) c #EB72F8", +"P) c #EB72F9", +"Q) c #E473F7", +"R) c #DC71F3", +"S) c #D36FF0", +"T) c #CC6EEB", +"U) c #C46CE8", +"V) c #BB6BE3", +"W) c #B36ADF", +"X) c #D92F63", +"Y) c #D52E61", +"Z) c #D22E5F", +"`) c #CE2D5D", +" ! c #AD468F", +".! c #5B5CB3", +"+! c #545AAF", +"@! c #4C59AB", +"#! c #4458A8", +"$! c #3C57A4", +"%! c #265398", +"&! c #1F5395", +"*! c #1E5D95", +"=! c #993542", +"-! c #1C7A93", +";! c #1A8592", +">! c #189892", +",! c #18A292", +"'! c #17A791", +")! c #16B291", +"!! c #15BD91", +"~! c #15C790", +"{! c #13D390", +"]! c #10F48F", +"^! c #10FF8F", +"/! c #2DFE8D", +"(! c #36FD8E", +"_! c #40FC8E", +":! c #49FB8D", +"~ c #EB71F6", +",~ c #EC72F9", +"'~ c #EA73FA", +")~ c #DA71F3", +"!~ c #D26FEE", +"~~ c #C96EEA", +"{~ c #8863CA", +"]~ c #8162C5", +"^~ c #7160BE", +"/~ c #615DB6", +"(~ c #525AAF", +"_~ c #4258A7", +":~ c #3B57A2", +"<~ c #33559F", +"[~ c #2B539B", +"}~ c #3B4982", +"|~ c #3A4E82", +"1~ c #3A5181", +"2~ c #395781", +"3~ c #2C608A", +"4~ c #1A8F92", +"5~ c #17A392", +"6~ c #17A991", +"7~ c #17B391", +"8~ c #15BF91", +"9~ c #14D590", +"0~ c #12E090", +"a~ c #11EA8F", +"b~ c #11FF8F", +"c~ c #2FFD8D", +"d~ c #39FC8E", +"e~ c #4BFB8D", +"f~ c #56FB8C", +"g~ c #68F98C", +"h~ c #73FA8C", +"i~ c #A3F78A", +"j~ c #B7F589", +"k~ c #C1F58A", +"l~ c #CAF48A", +"m~ c #D4F489", +"n~ c #DEF488", +"o~ c #F7ED8D", +"p~ c #F8D9A1", +"q~ c #F9D5A4", +"r~ c #FC9FDC", +"s~ c #98DCFF", +"t~ c #D26773", +"u~ c #D46D73", +"v~ c #A3FFFE", +"w~ c #A6FFF7", +"x~ c #A6FFF6", +"y~ c #A7FFF6", +"z~ c #EC6DEE", +"A~ c #EC6EEF", +"B~ c #EC6EF2", +"C~ c #EC70F3", +"D~ c #EC70F5", +"E~ c #EC71F7", +"F~ c #E572F8", +"G~ c #DA2F63", +"H~ c #A568D8", +"I~ c #8462C8", +"J~ c #6C5EBB", +"K~ c #655DB7", +"L~ c #5D5BB4", +"M~ c #555BB0", +"N~ c #275398", +"O~ c #13D191", +"P~ c #12DC90", +"Q~ c #10F28F", +"R~ c #10FD8F", +"S~ c #17FE8F", +"T~ c #21FE8D", +"U~ c #2BFD8D", +"V~ c #6EFA8C", +"W~ c #8BF88B", +"X~ c #B3F68A", +"Y~ c #CFF589", +"Z~ c #EDF389", +"`~ c #F7EB8E", +" { c #F8E596", +".{ c #FAD4A7", +"+{ c #FAD0AB", +"@{ c #F9CDAE", +"#{ c #FACAB1", +"${ c #FBBCC0", +"%{ c #FBB4C7", +"&{ c #FCAAD0", +"*{ c #D05D73", +"={ c #D46E73", +"-{ c #AFD8DC", +";{ c #D47375", +">{ c #EC3434", +",{ c #F91212", +"'{ c #FE0404", +"){ c #F81414", +"!{ c #E93E3E", +"~{ c #CD8C8B", +"{{ c #A8F3F0", +"]{ c #A6FFFA", +"^{ c #A5FFF9", +"/{ c #A5FFF8", +"({ c #A7FFF5", +"_{ c #A8FFF4", +":{ c #FFDCDC", +"<{ c #FF7575", +"[{ c #FF3434", +"}{ c #FF1212", +"|{ c #FF0404", +"1{ c #FF1414", +"2{ c #FF3E3E", +"3{ c #FF8C8C", +"4{ c #FFF3F3", +"5{ c #FFF1F1", +"6{ c #FF8B8B", +"7{ c #FF4040", +"8{ c #FF1919", +"9{ c #FF0306", +"0{ c #FF0204", +"a{ c #FE0811", +"b{ c #FB142C", +"c{ c #F82754", +"d{ c #F53D86", +"e{ c #EE5FCE", +"f{ c #EC73FA", +"g{ c #8F64CC", +"h{ c #6F5FBD", +"i{ c #605DB5", +"j{ c #4858AA", +"k{ c #9A2943", +"l{ c #983D42", +"m{ c #2C8187", +"n{ c #974142", +"o{ c #DD1616", +"p{ c #F90404", +"q{ c #F20908", +"r{ c #BF2E29", +"s{ c #517F6D", +"t{ c #16B691", +"u{ c #3CA978", +"v{ c #A94E34", +"w{ c #E7170F", +"x{ c #FB0402", +"y{ c #EC140B", +"z{ c #B64F2C", +"A{ c #4DC870", +"B{ c #27FD8D", +"C{ c #8BB967", +"D{ c #B97642", +"E{ c #D64929", +"F{ c #EB2716", +"G{ c #F80F08", +"H{ c #FD0402", +"I{ c #F8140B", +"J{ c #ED381F", +"K{ c #DE7542", +"L{ c #C9D778", +"M{ c #CCF58A", +"N{ c #DFF388", +"O{ c #EAF389", +"P{ c #F8DE89", +"Q{ c #FC6F47", +"R{ c #FE2116", +"S{ c #FF0604", +"T{ c #FF100C", +"U{ c #FD3D2E", +"V{ c #FB9675", +"W{ c #FD5754", +"X{ c #FD8CBB", +"Y{ c #D56F73", +"Z{ c #A2FAFE", +"`{ c #D17E7F", +" ] c #FE0202", +".] c #F61A19", +"+] c #BAC5C0", +"@] c #A8FFF3", +"#] c #A8FFF2", +"$] c #FFFEFE", +"%] c #FF7F7F", +"&] c #FF0202", +"*] c #FF1A1A", +"=] c #FFC5C5", +"-] c #FFDEDE", +";] c #FF1E1E", +">] c #F82A5B", +",] c #EC71F5", +"'] c #EC72F7", +")] c #9B66D3", +"!] c #9265CF", +"~] c #8A64CA", +"{] c #7A60C3", +"]] c #5B5BB3", +"^] c #545BB0", +"/] c #4457A8", +"(] c #9A2543", +"_] c #1F5895", +":] c #AD2E35", +"<] c #E11113", +"[] c #FE0101", +"}] c #70675A", +"|] c #5A7E68", +"1] c #F90504", +"2] c #FA0503", +"3] c #5CB565", +"4] c #2DFE8E", +"5] c #40FC8D", +"6] c #4AFB8D", +"7] c #DA361E", +"8] c #FD0704", +"9] c #D59F5A", +"0] c #C9F489", +"a] c #F9DA7C", +"b] c #FE2516", +"c] c #FC7B5E", +"d] c #FD5952", +"e] c #FBB2C7", +"f] c #FE5766", +"g] c #FF0102", +"h] c #D06073", +"i] c #D57073", +"j] c #D77070", +"k] c #FB0C0C", +"l] c #B6D3CC", +"m] c #A9FFF1", +"n] c #A9FFF0", +"o] c #A9FFEF", +"p] c #FF7070", +"q] c #FF0C0C", +"r] c #FFD3D3", +"s] c #FF5151", +"t] c #F8295A", +"u] c #EB70F5", +"v] c #9E67D4", +"w] c #9565D0", +"x] c #7E61C4", +"y] c #7560C0", +"z] c #5E5CB4", +"A] c #575AB0", +"B] c #4F59AC", +"C] c #9E2545", +"D] c #215295", +"E] c #1E5A94", +"F] c #FE0001", +"G] c #F60606", +"H] c #F10A09", +"I] c #E7190F", +"J] c #1BFA8B", +"K] c #1FFF8E", +"L] c #29FE8D", +"M] c #D7361E", +"N] c #FD0603", +"O] c #C6CE73", +"P] c #D8F488", +"Q] c #E2F287", +"R] c #FA3B21", +"S] c #FF0302", +"T] c #FD5B50", +"U] c #FBC3B7", +"V] c #FD5155", +"W] c #A6C0DF", +"X] c #A6C1DF", +"Y] c #D75A65", +"Z] c #A8C7DF", +"`] c #A8C8DF", +" ^ c #A8C9DF", +".^ c #A8CADF", +"+^ c #A9CCDF", +"@^ c #D95F65", +"#^ c #AAD2DF", +"$^ c #ABD3DF", +"%^ c #ABD4DF", +"&^ c #ACD5DF", +"*^ c #D57273", +"=^ c #B8C7C7", +"-^ c #DB6765", +";^ c #BAC3BE", +">^ c #B3DAD5", +",^ c #C0B5AF", +"'^ c #EE312F", +")^ c #EA3C39", +"!^ c #A7FFF2", +"~^ c #A8FFF1", +"{^ c #A9FFEE", +"]^ c #A9FFED", +"^^ c #FFDFDF", +"/^ c #FF6565", +"(^ c #FFC7C7", +"_^ c #FF6767", +":^ c #FFC3C3", +"<^ c #FFDADA", +"[^ c #FFB5B5", +"}^ c #FF3131", +"|^ c #FF3C3C", +"1^ c #FF0F0F", +"2^ c #FF7C7C", +"3^ c #F055BA", +"4^ c #EF5BC6", +"5^ c #F154B9", +"6^ c #F34598", +"7^ c #F72E64", +"8^ c #FC1125", +"9^ c #EC6EF1", +"0^ c #EE62D5", +"a^ c #EE62D7", +"b^ c #F02D60", +"c^ c #D861D1", +"d^ c #D160CD", +"e^ c #CA5FC9", +"f^ c #C35EC6", +"g^ c #B169DE", +"h^ c #A067D6", +"i^ c #9065CD", +"j^ c #7960C1", +"k^ c #625DB6", +"l^ c #A32648", +"m^ c #2B549B", +"n^ c #D3171C", +"o^ c #555D6F", +"p^ c #416A7A", +"q^ c #B8292E", +"r^ c #D2201D", +"s^ c #4F7F6F", +"t^ c #3F9378", +"u^ c #BB372A", +"v^ c #53B267", +"w^ c #1BFF8E", +"x^ c #25FE8D", +"y^ c #2EFE8E", +"z^ c #C14E2C", +"A^ c #B36538", +"B^ c #8E9F59", +"C^ c #7BC46D", +"D^ c #77D577", +"E^ c #7ED879", +"F^ c #92C26D", +"G^ c #BC7F47", +"H^ c #FC0704", +"I^ c #DE6338", +"J^ c #E1A85E", +"K^ c #FE2B19", +"L^ c #FAAD6A", +"M^ c #F9C17C", +"N^ c #FB7850", +"O^ c #FC5D4E", +"P^ c #FB9791", +"Q^ c #D16373", +"R^ c #D67373", +"S^ c #DE5D5C", +"T^ c #DD5F5D", +"U^ c #B1E2D8", +"V^ c #B8D2C6", +"W^ c #AAFFED", +"X^ c #AAFFEC", +"Y^ c #AAFFEB", +"Z^ c #FF5D5D", +"`^ c #FF5F5F", +" / c #FFE2E2", +"./ c #FFD2D2", +"+/ c #FF0707", +"@/ c #FFE6E6", +"#/ c #ED66E2", +"$/ c #EE68E3", +"%/ c #EE69E5", +"&/ c #ED69E6", +"*/ c #EC6AE9", +"=/ c #ED6CEB", +"-/ c #EE66DF", +";/ c #ED6DEE", +">/ c #EC6EF0", +",/ c #F33470", +"'/ c #D670F0", +")/ c #BD6BE4", +"!/ c #A468D8", +"~/ c #7460BF", +"{/ c #6D5EBB", +"]/ c #A8274A", +"^/ c #F90304", +"// c #356384", +"(/ c #446878", +"_/ c #318B83", +":/ c #3B937A", +"( c #ED6BEB", +",( c #F63370", +"'( c #E472F8", +")( c #DB71F4", +"!( c #D46FEF", +"~( c #C36DE7", +"{( c #AB68DB", +"]( c #A267D7", +"^( c #9264CF", +"/( c #8262C7", +"(( c #7A61C2", +"_( c #B2294F", +":( c #4C58AB", +"<( c #A1273F", +"[( c #1C6B94", +"}( c #1E7092", +"|( c #9F393E", +"1( c #198F92", +"2( c #955A41", +"3( c #12DE90", +"4( c #12E98F", +"5( c #17EC8B", +"6( c #B74C2A", +"7( c #EC683B", +"8( c #F7E788", +"9( c #FC6447", +"0( c #FAD6A4", +"a( c #FF0403", +"b( c #D26673", +"c( c #D36B73", +"d( c #D77370", +"e( c #D9736A", +"f( c #ABFFE9", +"g( c #ADFFE7", +"h( c #ACFFE6", +"i( c #ADFFE6", +"j( c #FFC2C2", +"k( c #FF4949", +"l( c #FD0F21", +"m( c #EE65DE", +"n( c #ED6BEA", +"o( c #ED6CEC", +"p( c #F6336E", +"q( c #EC71F6", +"r( c #E773FA", +"s( c #DF72F5", +"t( c #C66DE9", +"u( c #B66AE0", +"v( c #9E66D4", +"w( c #B62951", +"x( c #565BB1", +"y( c #3F57A5", +"z( c #9A2743", +"A( c #1F5B94", +"B( c #983B42", +"C( c #199192", +"D( c #965341", +"E( c #14CF90", +"F( c #13DA90", +"G( c #8D6E45", +"H( c #81F98B", +"I( c #FC0804", +"J( c #E6683B", +"K( c #CEF48A", +"L( c #D7F389", +"M( c #E2F388", +"N( c #F5EB84", +"O( c #FC6645", +"P( c #F8DF9C", +"Q( c #D46C73", +"R( c #A4FFFB", +"S( c #D7736F", +"T( c #A7FFF4", +"U( c #D77770", +"V( c #B4DFD1", +"W( c #B5DFD1", +"X( c #B5DFCF", +"Y( c #B6DFCE", +"Z( c #B5DFCE", +"`( c #B6DFCC", +" _ c #B2EED9", +"._ c #AEFFE5", +"+_ c #AEFFE4", +"@_ c #FF7777", +"#_ c #FFEEEE", +"$_ c #FFF7F7", +"%_ c #FFCDCD", +"&_ c #F4418E", +"*_ c #F6336F", +"=_ c #FC142D", +"-_ c #F44290", +";_ c #F6326D", +">_ c #EC73F9", +",_ c #EA73FB", +"'_ c #E272F6", +")_ c #DA70F3", +"!_ c #C26CE6", +"~_ c #BA6BE2", +"{_ c #B16ADE", +"]_ c #A969D9", +"^_ c #9965D2", +"/_ c #9164CD", +"(_ c #BC2A54", +"__ c #4A58AA", +":_ c #9C2544", +"<_ c #1F5894", +"[_ c #1B8592", +"}_ c #1A8F93", +"|_ c #964C41", +"1_ c #E01C13", +"2_ c #EF1109", +"3_ c #749C57", +"4_ c #4AD376", +"5_ c #48DD7C", +"6_ c #51DD7B", +"7_ c #B76036", +"8_ c #F21E11", +"9_ c #E94A2A", +"0_ c #DEF389", +"a_ c #EBCB72", +"b_ c #FC6842", +"c_ c #FE1B14", +"d_ c #D26973", +"e_ c #FD0404", +"f_ c #D6676D", +"g_ c #D86D6D", +"h_ c #D7736E", +"i_ c #A7FFF3", +"j_ c #DF5D58", +"k_ c #F12926", +"l_ c #ADF7E6", +"m_ c #ABFFEC", +"n_ c #AFF5DF", +"o_ c #CD9C8D", +"p_ c #ADFFE5", +"q_ c #ADFFE4", +"r_ c #AEFFE3", +"s_ c #AEFFE2", +"t_ c #AEFFE1", +"u_ c #FF6D6D", +"v_ c #FF2929", +"w_ c #FFF5F5", +"x_ c #FF9C9C", +"y_ c #FFE4E4", +"z_ c #EE60D4", +"A_ c #EE61D6", +"B_ c #F05CCC", +"C_ c #F7326D", +"D_ c #ED68E3", +"E_ c #ED68E5", +"F_ c #ED6AE7", +"G_ c #F72F67", +"H_ c #ED6FF1", +"I_ c #EC6FF4", +"J_ c #EB73F8", +"K_ c #EB74FB", +"L_ c #E673F8", +"M_ c #D56FF0", +"N_ c #C56DE7", +"O_ c #BC6BE4", +"P_ c #B46ADF", +"Q_ c #AC69DB", +"R_ c #A468D7", +"S_ c #C02B57", +"T_ c #6D5EBC", +"U_ c #645DB7", +"V_ c #A12647", +"W_ c #993443", +"X_ c #964941", +"Y_ c #16AF91", +"Z_ c #FA0403", +"`_ c #9D643B", +" : c #0FFD8E", +".: c #2DFA8C", +"+: c #DA2F1A", +"@: c #6FF98C", +"#: c #79F98B", +"$: c #D5542F", +"%: c #FB0B06", +"&: c #B9E37F", +"*: c #EE7240", +"=: c #FB6A40", +"-: c #F8E893", +";: c #F8E19A", +">: c #FD5139", +",: c #A5CEE6", +"': c #9FEEFF", +"): c #F61718", +"!: c #F32021", +"~: c #B6BDC4", +"{: c #ADD8DF", +"]: c #ADD9DF", +"^: c #A9E4EA", +"/: c #F61818", +"(: c #F32121", +"_: c #B9C4C2", +":: c #AFDFDC", +"<: c #B0DFDB", +"[: c #ADEAE5", +"}: c #A7FFF7", +"|: c #D8736E", +"1: c #BCC6BA", +"2: c #EF312D", +"3: c #C9A597", +"4: c #B9D5C3", +"5: c #B8D9C5", +"6: c #BFC6B4", +"7: c #CD9A8C", +"8: c #E25850", +"9: c #FA0E0D", +"0: c #ADFFE3", +"a: c #AFFFE1", +"b: c #AFFFE0", +"c: c #FF1818", +"d: c #FF2121", +"e: c #FFC4C4", +"f: c #FFEAEA", +"g: c #FFC6C6", +"h: c #FFA5A5", +"i: c #FFD5D5", +"j: c #FFD9D9", +"k: c #FF9A9A", +"l: c #FF5858", +"m: c #FF0E0E", +"n: c #FF8484", +"o: c #F152B6", +"p: c #F24CA8", +"q: c #F82857", +"r: c #F63779", +"s: c #EE67E0", +"t: c #ED68E4", +"u: c #FD0A16", +"v: c #FD0E1F", +"w: c #F055B8", +"x: c #EE61D3", +"y: c #EE61D5", +"z: c #E873FA", +"A: c #E072F6", +"B: c #D970F1", +"C: c #D16EED", +"D: c #C86DE9", +"E: c #C52C59", +"F: c #A62749", +"G: c #993143", +"H: c #974542", +"I: c #E7130F", +"J: c #E9150E", +"K: c #5DA161", +"L: c #33D57A", +"M: c #4FBD6B", +"N: c #C14627", +"O: c #62FA8C", +"P: c #6BFA8C", +"Q: c #9FAD61", +"R: c #EB361E", +"S: c #C3B968", +"T: c #C3CF74", +"U: c #DF834A", +"V: c #FE0302", +"W: c #FC6C3E", +"X: c #F7ED8E", +"Y: c #FAAF76", +"Z: c #EB2E33", +"`: c #E14C50", +" < c #E15253", +".< c #E25050", +"+< c #E25351", +"@< c #A6FFF5", +"#< c #D8736C", +"$< c #A8FFF0", +"%< c #AAFFEE", +"&< c #DA7068", +"*< c #AFFFDF", +"=< c #B0FFDE", +"-< c #FF5050", +";< c #FF5353", +">< c #FF0103", +",< c #F155BA", +"'< c #ED66E0", +")< c #F92148", +"!< c #F9244F", +"~< c #EC71F8", +"{< c #E372F8", +"]< c #DC71F4", +"^< c #D370EF", +"/< c #CB6EEC", +"(< c #C36CE7", +"_< c #BA6CE3", +":< c #CA2D5B", +"<< c #7360BF", +"[< c #AB284C", +"}< c #3C56A3", +"|< c #3455A0", +"1< c #255298", +"2< c #992E43", +"3< c #974242", +"4< c #9D473E", +"5< c #EE130B", +"6< c #4AFC8D", +"7< c #68F98A", +"8< c #DC3D22", +"9< c #FD0804", +"0< c #F86D3D", +"a< c #F8EB90", +"b< c #FC6442", +"c< c #EB2F33", +"d< c #D36D73", +"e< c #B6BEC5", +"f< c #B9C5C3", +"g< c #E25350", +"h< c #D8736B", +"i< c #ABFEE9", +"j< c #D48479", +"k< c #F61C19", +"l< c #B0FFE0", +"m< c #B0FFDF", +"n< c #B1FFDE", +"o< c #B1FFDC", +"p< c #FF1C1C", +"q< c #FF0203", +"r< c #F53B82", +"s< c #EE63D9", +"t< c #EE64DC", +"u< c #F150AE", +"v< c #F9234E", +"w< c #CF6EEC", +"x< c #C66DE8", +"y< c #CF2E5E", +"z< c #8E63CC", +"A< c #B0284E", +"B< c #9A2B43", +"C< c #1C6F94", +"D< c #1C7393", +"E< c #973F42", +"F< c #279588", +"G< c #D91B18", +"H< c #C23D24", +"I< c #976D3E", +"J< c #59FA8D", +"K< c #72E27F", +"L< c #E92615", +"M< c #FD0503", +"N< c #CBA35B", +"O< c #F1713F", +"P< c #ECF388", +"Q< c #F5F289", +"R< c #F7F08B", +"S< c #F8E790", +"T< c #FB774D", +"U< c #FF0402", +"V< c #EC2F33", +"W< c #B6C2C7", +"X< c #E14F51", +"Y< c #F61919", +"Z< c #E15353", +"`< c #BAC7C2", +" [ c #E3514F", +".[ c #F61918", +"+[ c #E3534F", +"@[ c #ADFFE8", +"#[ c #B6E2CD", +"$[ c #D67E72", +"%[ c #EC3C36", +"&[ c #F81513", +"*[ c #FD0504", +"=[ c #FC0807", +"-[ c #EA433B", +";[ c #D97A6C", +">[ c #C2C1AA", +",[ c #AFFEDF", +"'[ c #B1FFDD", +")[ c #B1FFDB", +"![ c #FF7E7E", +"~[ c #FF0808", +"{[ c #FF4343", +"][ c #FF7A7A", +"^[ c #FFC1C1", +"/[ c #FFCACA", +"([ c #FF5757", +"_[ c #FF2E2E", +":[ c #FF1111", +"<[ c #FE0710", +"[[ c #FB152E", +"}[ c #F72C62", +"|[ c #F052B6", +"1[ c #EE62D9", +"2[ c #EE64DB", +"3[ c #EE65DD", +"4[ c #ED65DF", +"5[ c #F150B0", +"6[ c #F9234D", +"7[ c #EB72FA", +"8[ c #E271F6", +"9[ c #C96EEB", +"0[ c #D42E61", +"a[ c #9866D2", +"b[ c #8062C6", +"c[ c #B52950", +"d[ c #4358A6", +"e[ c #9A2843", +"f[ c #1D6C93", +"g[ c #983C42", +"h[ c #24978B", +"i[ c #914D45", +"j[ c #DD1815", +"k[ c #F30A07", +"l[ c #D2271C", +"m[ c #876D49", +"n[ c #19D98B", +"o[ c #946A40", +"p[ c #4CFC8D", +"q[ c #67EF86", +"r[ c #B77843", +"s[ c #EB2314", +"t[ c #F71109", +"u[ c #DC4D2C", +"v[ c #AEC66E", +"w[ c #ACDF7D", +"x[ c #E7854B", +"y[ c #F8F18A", +"z[ c #F9CC82", +"A[ c #A1F4FF", +"B[ c #A3FAFF", +"C[ c #AFFFE2", +"D[ c #B0FFDD", +"E[ c #B2FFDC", +"F[ c #B2FFDA", +"G[ c #B2FFD9", +"H[ c #F05BC8", +"I[ c #F05BCA", +"J[ c #F05CCB", +"K[ c #EF5DCD", +"L[ c #EF5ECF", +"M[ c #EF5FD1", +"N[ c #EF60D3", +"O[ c #EF60D4", +"P[ c #EE62D6", +"Q[ c #EE62D8", +"R[ c #EE63DA", +"S[ c #ED67E2", +"T[ c #ED67E3", +"U[ c #ED69E5", +"V[ c #ED69E7", +"W[ c #ED6EF0", +"X[ c #EC74FB", +"Y[ c #8B63CB", +"Z[ c #2E549D", +"`[ c #1F5694", +" } c #1F5C94", +".} c #1A8E92", +"+} c #189792", +"@} c #15BA90", +"#} c #14C691", +"$} c #13D090", +"%} c #13DC90", +"&} c #11E78F", +"*} c #11F28F", +"=} c #5CFB8C", +"-} c #83F98B", +";} c #C38E4F", +">} c #D4BC6A", +",} c #ABFFE8", +"'} c #B2FFDB", +")} c #B3FFDA", +"!} c #B3FFD9", +"~} c #B3FFD8", +"{} c #EF59C5", +"]} c #F05AC7", +"^} c #EF5BC9", +"/} c #EF5CCB", +"(} c #EF5ED0", +"_} c #EF5FD2", +":} c #EE60D3", +"<} c #EF61D6", +"[} c #EE63DB", +"}} c #EE64DD", +"|} c #EE67E3", +"1} c #EC6AE7", +"2} c #ED6BEC", +"3} c #ED6CED", +"4} c #EC6DF0", +"5} c #EB71F4", +"6} c #D870F2", +"7} c #D16FEE", +"8} c #C06CE5", +"9} c #AF6ADD", +"0} c #A768D9", +"a} c #8762C9", +"b} c #7E61C5", +"c} c #4157A5", +"d} c #31549E", +"e} c #1E6495", +"f} c #1A8692", +"g} c #15C190", +"h} c #14CC91", +"i} c #11EE8F", +"j} c #A27843", +"k} c #AB713F", +"l} c #77C16C", +"m} c #5DF488", +"n} c #6BF98B", +"o} c #7EE982", +"p} c #B58F50", +"q} c #FB0804", +"r} c #F22917", +"s} c #B8F588", +"t} c #CDF589", +"u} c #EAF289", +"v} c #ACFFE5", +"w} c #B0FFE1", +"x} c #B4FFD7", +"y} c #B5FFD6", +"z} c #F058C3", +"A} c #F059C5", +"B} c #F059C7", +"C} c #EF5BC8", +"D} c #EF5BCA", +"E} c #EF5CCC", +"F} c #EF5DCE", +"G} c #EE61D4", +"H} c #EF62D7", +"I} c #EE64DA", +"J} c #EE68E4", +"K} c #EC6AE8", +"L} c #EC6DEF", +"M} c #E472F7", +"N} c #DC70F3", +"O} c #B26ADF", +"P} c #AA68DB", +"Q} c #A268D6", +"R} c #9A66D3", +"S} c #6B5FBA", +"T} c #1D7094", +"U} c #1B7593", +"V} c #16BD90", +"W} c #14C890", +"X} c #14D390", +"Y} c #11E990", +"Z} c #AD6237", +"`} c #FD0302", +" | c #F1150C", +".| c #F2140B", +"+| c #B6C16C", +"@| c #DCF388", +"#| c #E6F389", +"$| c #AFFFE3", +"%| c #B3FFD7", +"&| c #B4FFD6", +"*| c #B5FFD5", +"=| c #F056C0", +"-| c #F057C2", +";| c #F059C4", +">| c #EF59C6", +",| c #EF5DCC", +"'| c #EF5DCF", +")| c #EF5FD0", +"!| c #EF62D8", +"~| c #EE67E1", +"{| c #ED6BE8", +"]| c #ED6EEF", +"^| c #DE71F5", +"/| c #CE6FEC", +"(| c #C66EE9", +"_| c #BE6BE5", +":| c #9D66D4", +"<| c #9566D0", +"[| c #565BB0", +"}| c #4758A8", +"|| c #3756A1", +"1| c #189C93", +"2| c #18A691", +"3| c #17AD91", +"4| c #16B991", +"5| c #12DA90", +"6| c #12E58F", +"7| c #10F08F", +"8| c #0FFB8E", +"9| c #16FF8E", +"0| c #A86337", +"a| c #ED2414", +"b| c #A5C06B", +"c| c #BBF589", +"d| c #CEF589", +"e| c #E2F389", +"f| c #B4FFD5", +"g| c #B5FFD4", +"h| c #F056BD", +"i| c #F158C1", +"j| c #F059C3", +"k| c #EF5AC7", +"l| c #EF60D2", +"m| c #EE61D7", +"n| c #EE65DC", +"o| c #ED65DE", +"p| c #EE66E0", +"q| c #EE68E2", +"r| c #EC6BEB", +"s| c #ED6DED", +"t| c #DA70F2", +"u| c #CA6EEA", +"v| c #C26DE6", +"w| c #B96CE2", +"x| c #A869DA", +"y| c #A067D5", +"z| c #9866D1", +"A| c #9065CE", +"B| c #7961C2", +"C| c #4A59AA", +"D| c #4257A7", +"E| c #33559E", +"F| c #2B549A", +"G| c #16B392", +"H| c #15BF90", +"I| c #15CB90", +"J| c #13D590", +"K| c #10F78F", +"L| c #3BD275", +"M| c #907C45", +"N| c #C54426", +"O| c #E71D10", +"P| c #F90804", +"Q| c #F40F08", +"R| c #E42817", +"S| c #C65A32", +"T| c #9AA75E", +"U| c #76F389", +"V| c #9AF88B", +"W| c #AEF78A", +"X| c #CBF589", +"Y| c #A9FFF2", +"Z| c #AAFFEF", +"`| c #B6FFD3", +" 1 c #B6FFD2", +".1 c #F154BC", +"+1 c #F155BD", +"@1 c #F056BF", +"#1 c #F057C0", +"$1 c #F058C2", +"%1 c #EF59C4", +"&1 c #F059C6", +"*1 c #F05BC9", +"=1 c #EE5FD1", +"-1 c #EF61D4", +";1 c #EE68E5", +">1 c #EC6CEB", +",1 c #EC6CEC", +"'1 c #CD6EEB", +")1 c #BC6CE4", +"!1 c #7B60C3", +"~1 c #745FC0", +"{1 c #5D5BB3", +"]1 c #555AAF", +"^1 c #3555A1", +"/1 c #275298", +"(1 c #14C690", +"_1 c #14D190", +":1 c #12DC8F", +"<1 c #11E790", +"[1 c #10FE8E", +"}1 c #35FD8D", +"|1 c #A0F68A", +"11 c #B3F58A", +"21 c #BDF589", +"31 c #B5FFD3", +"41 c #B7FFD1", +"51 c #F153B9", +"61 c #F154BA", +"71 c #F054BC", +"81 c #F056BE", +"91 c #F057C1", +"01 c #F058C4", +"a1 c #EF5CCA", +"b1 c #ED68E2", +"c1 c #D06EEE", +"d1 c #C86EE9", +"e1 c #B06ADD", +"f1 c #A769D9", +"g1 c #585BB2", +"h1 c #5059AD", +"i1 c #4158A6", +"j1 c #1E5595", +"k1 c #10F98F", +"l1 c #14FF8E", +"m1 c #1EFE8E", +"n1 c #27FD8E", +"o1 c #31FD8D", +"p1 c #4EFB8D", +"q1 c #75FA8C", +"r1 c #7EF88C", +"s1 c #89F88B", +"t1 c #92F88A", +"u1 c #B9F689", +"v1 c #B4FFD8", +"w1 c #B6FFD5", +"x1 c #B7FFD3", +"y1 c #B7FFD2", +"z1 c #B7FFD0", +"A1 c #B8FFD0", +"B1 c #B8FFCF", +"C1 c #F152B8", +"D1 c #F055BC", +"E1 c #F055BD", +"F1 c #F157C1", +"G1 c #F05AC6", +"H1 c #EF5AC8", +"I1 c #EE5FD3", +"J1 c #EF60D5", +"K1 c #EF61D7", +"L1 c #EF62D9", +"M1 c #ED66DE", +"N1 c #EE67E2", +"O1 c #EB71F8", +"P1 c #EB73F9", +"Q1 c #EC73FB", +"R1 c #C36DE6", +"S1 c #9A66D2", +"T1 c #8A64CB", +"U1 c #6A5EBB", +"V1 c #4B59AB", +"W1 c #1E6295", +"X1 c #1D6B93", +"Y1 c #14C891", +"Z1 c #12DE8F", +"`1 c #2DFD8D", +" 2 c #37FD8E", +".2 c #4AFC8C", +"+2 c #54FB8C", +"@2 c #5EFB8C", +"#2 c #71F98B", +"$2 c #84F88C", +"%2 c #98F88B", +"&2 c #A2F78A", +"*2 c #B8FFCE", +"=2 c #B9FFCD", +"-2 c #F150B3", +";2 c #F153B7", +">2 c #F053B9", +",2 c #F154BB", +"'2 c #F055BF", +")2 c #F157C0", +"!2 c #EF5CCD", +"~2 c #EF5ED1", +"{2 c #EE63D8", +"]2 c #D76FF1", +"^2 c #B56BE0", +"/2 c #30559D", +"(2 c #205195", +"_2 c #1D5F95", +":2 c #1A8293", +"<2 c #198C93", +"[2 c #199C92", +"}2 c #15B891", +"|2 c #14C390", +"12 c #13CF90", +"22 c #13DA8F", +"32 c #11F08F", +"42 c #29FE8E", +"52 c #3DFD8D", +"62 c #50FB8D", +"72 c #5AFA8C", +"82 c #6EF98C", +"92 c #77FA8B", +"02 c #94F78B", +"a2 c #BAFFCE", +"b2 c #BAFFCC", +"c2 c #F14FB1", +"d2 c #F251B5", +"e2 c #F153B8", +"f2 c #EF5AC6", +"g2 c #EF61D5", +"h2 c #EE61D8", +"i2 c #ED68E6", +"j2 c #EC6DED", +"k2 c #EB72F7", +"l2 c #D16FEF", +"m2 c #B96CE1", +"n2 c #B06ADE", +"o2 c #9165CD", +"p2 c #715FBD", +"q2 c #1C6D93", +"r2 c #1C7794", +"s2 c #13D58F", +"t2 c #12E190", +"u2 c #12FF8F", +"v2 c #25FD8E", +"w2 c #39FD8D", +"x2 c #60FA8C", +"y2 c #73F98C", +"z2 c #7DF88B", +"A2 c #B6FFD4", +"B2 c #B6FFD1", +"C2 c #B9FFCF", +"D2 c #BAFFCD", +"E2 c #BBFFCC", +"F2 c #F24EAF", +"G2 c #F14FB0", +"H2 c #F150B2", +"I2 c #F151B3", +"J2 c #F152B5", +"K2 c #F152B7", +"L2 c #F054B9", +"M2 c #F156BF", +"N2 c #EF5ECD", +"O2 c #EE60D5", +"P2 c #ED67E4", +"Q2 c #D570EF", +"R2 c #AB69DC", +"S2 c #5C5BB3", +"T2 c #555BAF", +"U2 c #4D5AAC", +"V2 c #4657A8", +"W2 c #3655A1", +"X2 c #265298", +"Y2 c #1A8892", +"Z2 c #16B091", +"`2 c #15C691", +" 3 c #13D190", +".3 c #17FF8F", +"+3 c #52FC8D", +"@3 c #6FF98B", +"#3 c #79F98C", +"$3 c #ABFFED", +"%3 c #B8FFD1", +"&3 c #BBFFCB", +"*3 c #BBFFCA", +"=3 c #F24DAC", +"-3 c #F14EAE", +";3 c #F14EAF", +">3 c #F151B5", +",3 c #F155BC", +"'3 c #F05AC5", +")3 c #EF5BCB", +"!3 c #EE66DE", +"~3 c #EE66E1", +"{3 c #B76AE1", +"]3 c #AF69DD", +"^3 c #9F67D4", +"/3 c #8E64CD", +"(3 c #8663C9", +"_3 c #6F5FBC", +":3 c #675EB9", +"<3 c #505AAD", +"[3 c #4057A6", +"}3 c #1D6993", +"|3 c #1D6E94", +"13 c #1B7D94", +"23 c #15B692", +"33 c #13D88F", +"43 c #10EE8F", +"53 c #27FE8D", +"63 c #31FD8E", +"73 c #3BFC8E", +"83 c #4EFC8D", +"93 c #58FB8D", +"03 c #6BFA8B", +"a3 c #75F98B", +"b3 c #B9FFCC", +"c3 c #BAFFCB", +"d3 c #BCFFC9", +"e3 c #F24CA9", +"f3 c #F24CAB", +"g3 c #F24EAD", +"h3 c #F153BA", +"i3 c #F055BE", +"j3 c #F058C1", +"k3 c #EF5ECE", +"l3 c #EE60D1", +"m3 c #ED6AE9", +"n3 c #E372F7", +"o3 c #DB71F3", +"p3 c #BB6CE2", +"q3 c #B369DF", +"r3 c #AA68DA", +"s3 c #9265CE", +"t3 c #8964CA", +"u3 c #7260BE", +"v3 c #635DB6", +"w3 c #3C56A4", +"x3 c #2C539C", +"y3 c #1C7694", +"z3 c #1C7B94", +"A3 c #17A292", +"B3 c #17A891", +"C3 c #10F58F", +"D3 c #1AFE8F", +"E3 c #2DFD8E", +"F3 c #37FD8D", +"G3 c #5EFA8C", +"H3 c #B9FFCE", +"I3 c #BBFFC9", +"J3 c #BDFFC8", +"K3 c #F24AA7", +"L3 c #F34BA9", +"M3 c #F34DAA", +"N3 c #F24FB0", +"O3 c #F24FB2", +"P3 c #F250B3", +"Q3 c #F054BA", +"R3 c #ED66E1", +"S3 c #ED6AE6", +"T3 c #ED6CEA", +"U3 c #E672F9", +"V3 c #DF71F4", +"W3 c #CE6EEC", +"X3 c #B66BE0", +"Y3 c #AE69DC", +"Z3 c #8D64CB", +"`3 c #8562C7", +" 4 c #4E59AD", +".4 c #1C7E94", +"+4 c #199293", +"@4 c #18A091", +"#4 c #15C490", +"$4 c #12E590", +"%4 c #10FB8F", +"&4 c #3CFD8E", +"*4 c #50FC8C", +"=4 c #B2FFD8", +"-4 c #BAFFCA", +";4 c #BCFFC8", +">4 c #BDFFC7", +",4 c #BEFFC7", +"'4 c #F349A4", +")4 c #F24AA6", +"!4 c #F24BA8", +"~4 c #F24BAA", +"{4 c #F24DAD", +"]4 c #F14FAF", +"^4 c #F24FB1", +"/4 c #F156BD", +"(4 c #F05AC9", +"_4 c #EF63D9", +":4 c #EC6BEA", +"<4 c #D971F2", +"[4 c #A167D6", +"}4 c #9865D2", +"|4 c #7860C2", +"14 c #245296", +"24 c #1E5994", +"34 c #16B491", +"44 c #14CB90", +"54 c #11EC8F", +"64 c #2FFD8E", +"74 c #BCFFCA", +"84 c #BEFFC6", +"94 c #F348A2", +"04 c #F24AA5", +"a4 c #F24BA7", +"b4 c #F24BA9", +"c4 c #F24DAB", +"d4 c #F251B4", +"e4 c #EE66DD", +"f4 c #ED6CEE", +"g4 c #D570F0", +"h4 c #CC6EEC", +"i4 c #A367D8", +"j4 c #8B64CB", +"k4 c #4557A8", +"l4 c #1F5C95", +"m4 c #18A791", +"n4 c #17B091", +"o4 c #16BB91", +"p4 c #11F28E", +"q4 c #BFFFC5", +"r4 c #F3469F", +"s4 c #F248A1", +"t4 c #F349A3", +"u4 c #F24CAA", +"v4 c #F24DAE", +"w4 c #F24FAF", +"x4 c #F150B1", +"y4 c #ED66DF", +"z4 c #ED69E8", +"A4 c #EC72FA", +"B4 c #E072F5", +"C4 c #9F66D5", +"D4 c #8763C8", +"E4 c #7F61C4", +"F4 c #7760C0", +"G4 c #4859AA", +"H4 c #16B791", +"I4 c #12D890", +"J4 c #0FFA8F", +"K4 c #13FE8F", +"L4 c #BCFFCB", +"M4 c #BFFFC6", +"N4 c #C0FFC4", +"O4 c #F3459D", +"P4 c #F3479E", +"Q4 c #F347A1", +"R4 c #F248A2", +"S4 c #F24EAE", +"T4 c #F053B8", +"U4 c #EE5FD2", +"V4 c #D36FEF", +"W4 c #9A65D3", +"X4 c #9165CE", +"Y4 c #8A63CA", +"Z4 c #8162C7", +"`4 c #7960C3", +" 5 c #535AAE", +".5 c #3C57A3", +"+5 c #1C7B93", +"@5 c #1A8093", +"#5 c #15C891", +"$5 c #12EA90", +"%5 c #10FF8E", +"&5 c #C0FFC5", +"*5 c #C1FFC3", +"=5 c #F3449A", +"-5 c #F4459C", +";5 c #F3469E", +">5 c #F346A0", +",5 c #F249A3", +"'5 c #F249A5", +")5 c #F34AA6", +"!5 c #F250B2", +"~5 c #EE65DF", +"{5 c #ED6DEF", +"]5 c #B56AE0", +"^5 c #9565CF", +"/5 c #8463C8", +"(5 c #1D6094", +"_5 c #17A092", +":5 c #15B991", +"<5 c #15C390", +"[5 c #14CF91", +"}5 c #12E690", +"|5 c #C0FFC3", +"15 c #C1FFC4", +"25 c #C2FFC3", +"35 c #F34297", +"45 c #F44499", +"55 c #F4459B", +"65 c #F3469D", +"75 c #F347A0", +"85 c #F348A3", +"95 c #F34AA4", +"05 c #F34BAA", +"a5 c #F250B1", +"b5 c #F151B4", +"c5 c #F156C0", +"d5 c #F05CCA", +"e5 c #E172F7", +"f5 c #C16CE6", +"g5 c #A968D9", +"h5 c #9865D1", +"i5 c #8061C5", +"j5 c #7861C2", +"k5 c #695DB9", +"l5 c #3A56A3", +"m5 c #32559F", +"n5 c #235397", +"o5 c #198B92", +"p5 c #18A391", +"q5 c #17A992", +"r5 c #10F78E", +"s5 c #BDFFC6", +"t5 c #C1FFC2", +"u5 c #C2FFC2", +"v5 c #C3FFC3", +"w5 c #F34295", +"x5 c #F44297", +"y5 c #F4449B", +"z5 c #F3459C", +"A5 c #F14DAC", +"B5 c #F14EB0", +"C5 c #F150B4", +"D5 c #F155BB", +"E5 c #F05BC7", +"F5 c #EF5CC9", +"G5 c #EE5ED1", +"H5 c #C46DE7", +"I5 c #BC6BE3", +"J5 c #8362C6", +"K5 c #7C60C3", +"L5 c #6C5FBB", +"M5 c #4D59AB", +"N5 c #1E6195", +"O5 c #1A9392", +"P5 c #199893", +"Q5 c #15BB91", +"R5 c #15C690", +"S5 c #11E890", +"T5 c #BDFFC9", +"U5 c #C3FFC2", +"V5 c #C4FFC2", +"W5 c #C4FFC3", +"X5 c #C5FFC4", +"Y5 c #F44193", +"Z5 c #F44195", +"`5 c #F44396", +" 6 c #F44397", +".6 c #F3459B", +"+6 c #F348A1", +"@6 c #F349A5", +"#6 c #F14FB2", +"$6 c #F054BB", +"%6 c #F154BD", +"&6 c #EE60D2", +"*6 c #E873F9", +"=6 c #CF6FEE", +"-6 c #BF6CE5", +";6 c #A668D9", +">6 c #675DB9", +",6 c #3856A2", +"'6 c #31559E", +")6 c #17A592", +"!6 c #13CC91", +"~6 c #C5FFC3", +"{6 c #C6FFC4", +"]6 c #C7FFC3", +"^6 c #C7FFC4", +"/6 c #F43F90", +"(6 c #F44092", +"_6 c #F34397", +":6 c #F34499", +"<6 c #F05AC8", +"[6 c #BA6BE3", +"}6 c #AA69DA", +"|6 c #8963CB", +"16 c #5B5CB2", +"26 c #535BAE", +"36 c #4B58AB", +"46 c #4457A7", +"56 c #189993", +"66 c #BFFFC4", +"76 c #C6FFC3", +"86 c #C8FFC4", +"96 c #C9FFC5", +"06 c #CAFFC5", +"a6 c #F43E8E", +"b6 c #F53E8F", +"c6 c #F44091", +"d6 c #F34398", +"e6 c #F2479F", +"f6 c #F347A2", +"g6 c #F151B6", +"h6 c #F155BE", +"i6 c #ED65DD", +"j6 c #EC6CEE", +"k6 c #D66FF1", +"l6 c #A667D8", +"m6 c #9D67D4", +"n6 c #5E5CB5", +"o6 c #565AB1", +"p6 c #4E5AAD", +"q6 c #C9FFC4", +"r6 c #CBFFC5", +"s6 c #CCFFC6", +"t6 c #F43C8B", +"u6 c #F53E8D", +"v6 c #F43E8F", +"w6 c #F43F91", +"x6 c #F44295", +"y6 c #F252B6", +"z6 c #EE69E6", +"A6 c #A869D9", +"B6 c #9064CD", +"C6 c #685DBA", +"D6 c #1E5995", +"E6 c #C8FFC5", +"F6 c #CDFFC6", +"G6 c #CEFFC7", +"H6 c #F43C89", +"I6 c #F53C8A", +"J6 c #F53D8C", +"K6 c #F43F8E", +"L6 c #F44194", +"M6 c #F34195", +"N6 c #F4459A", +"O6 c #F34AA5", +"P6 c #F24EAC", +"Q6 c #F058C5", +"R6 c #E573F8", +"S6 c #B36BE0", +"T6 c #9464CE", +"U6 c #3D56A4", +"V6 c #265299", +"W6 c #1C7A94", +"X6 c #1B8492", +"Y6 c #18A191", +"Z6 c #CDFFC5", +"`6 c #CEFFC6", +" 7 c #CFFFC6", +".7 c #D1FFC7", +"+7 c #F53A86", +"@7 c #F53B88", +"#7 c #F53D89", +"$7 c #F43D8C", +"%7 c #F43E8D", +"&7 c #F44090", +"*7 c #F34296", +"=7 c #F247A1", +"-7 c #EF62D6", +";7 c #E071F5", +">7 c #D770F2", +",7 c #C76EEA", +"'7 c #9665D1", +")7 c #4859A9", +"!7 c #31559D", +"~7 c #295399", +"{7 c #1D7393", +"]7 c #198C92", +"^7 c #BFFFC7", +"/7 c #CFFFC7", +"(7 c #D0FFC7", +"_7 c #D2FFC8", +":7 c #D3FFC8", +"<7 c #F53983", +"[7 c #F53A85", +"}7 c #F53B87", +"|7 c #F53C89", +"17 c #F43D8B", +"27 c #F53E8C", +"37 c #F4449A", +"47 c #F247A0", +"57 c #F249A2", +"67 c #F249A4", +"77 c #F34CA9", +"87 c #8161C6", +"97 c #7A61C3", +"07 c #5A5CB3", +"a7 c #2D549B", +"b7 c #F5F5FF", +"c7 c #BDBDFF", +"d7 c #9A9AFF", +"e7 c #8A8AFF", +"f7 c #B2B2FF", +"g7 c #BEFFC5", +"h7 c #CAFFC4", +"i7 c #CBFFC6", +"j7 c #D4FFC9", +"k7 c #F63882", +"l7 c #F53984", +"m7 c #F53985", +"n7 c #F43C8A", +"o7 c #F53D8B", +"p7 c #F53E8E", +"q7 c #F44294", +"r7 c #F44399", +"s7 c #F248A3", +"t7 c #F24DAF", +"u7 c #F05DCD", +"v7 c #EF5FD3", +"w7 c #E673F9", +"x7 c #DE71F4", +"y7 c #C56DE9", +"z7 c #B56AE1", +"A7 c #A567D8", +"B7 c #8175E4", +"C7 c #2F549D", +"D7 c #198D92", +"E7 c #F1F1FF", +"F7 c #9292FF", +"G7 c #B7FFCF", +"H7 c #D5FFC9", +"I7 c #D6FFC9", +"J7 c #D7FFCA", +"K7 c #BEBEFF", +"L7 c #F53780", +"M7 c #F53881", +"N7 c #F63B85", +"O7 c #B864CA", +"P7 c #B865CB", +"Q7 c #B765CC", +"R7 c #B865CC", +"S7 c #D355B1", +"T7 c #F44296", +"U7 c #F44398", +"V7 c #ED64DD", +"W7 c #C96EE9", +"X7 c #C16CE5", +"Y7 c #B96BE1", +"Z7 c #B16ADD", +"`7 c #8676E7", +" 8 c #7961C1", +".8 c #33549E", +"+8 c #235396", +"@8 c #1F5995", +"#8 c #1E6894", +"$8 c #B1B1FF", +"%8 c #D8FFCA", +"&8 c #D9FFCB", +"*8 c #F5367E", +"=8 c #F53880", +"-8 c #F63881", +";8 c #F53882", +">8 c #F53A83", +",8 c #F3449B", +"'8 c #F24DAA", +")8 c #F250B4", +"!8 c #EC6CEA", +"~8 c #EB70F3", +"{8 c #D46FF0", +"]8 c #8B77E9", +"^8 c #735FBF", +"/8 c #545AB0", +"(8 c #1D6195", +"_8 c #8E8EFF", +":8 c #9C9CFF", +"<8 c #E9E9FF", +"[8 c #F0F0FF", +"}8 c #C5FFC2", +"|8 c #CAFFC6", +"18 c #8D90FA", +"28 c #DAFFCB", +"38 c #DBFFCB", +"48 c #F6367C", +"58 c #F6367E", +"68 c #F5377F", +"78 c #F63983", +"88 c #F43F8F", +"98 c #F24BA6", +"08 c #D871F1", +"a8 c #B870E8", +"b8 c #B16DE5", +"c8 c #AA6DE1", +"d8 c #A36CDD", +"e8 c #9D69D8", +"f8 c #8662C9", +"g8 c #225295", +"h8 c #BCBCFF", +"i8 c #CCFFC5", +"j8 c #C6F0CE", +"k8 c #C7F0CE", +"l8 c #C7F0CF", +"m8 c #C8F0CE", +"n8 c #C9F0CF", +"o8 c #CAF0CF", +"p8 c #CBF0CF", +"q8 c #C4E5D4", +"r8 c #898CFC", +"s8 c #8687FE", +"t8 c #C6E2D7", +"u8 c #DBFFCC", +"v8 c #DCFFCC", +"w8 c #B5B5FF", +"x8 c #F7F7FF", +"y8 c #F6357A", +"z8 c #F5357C", +"A8 c #F6367D", +"B8 c #B864C9", +"C8 c #F53D8D", +"D8 c #F53F90", +"E8 c #F44192", +"F8 c #F251B6", +"G8 c #ED6DEC", +"H8 c #D36FEE", +"I8 c #A968DB", +"J8 c #635CB7", +"K8 c #EDEDFF", +"L8 c #9D9DFF", +"M8 c #8787FF", +"N8 c #888DFC", +"O8 c #8F9DF5", +"P8 c #9CBBE9", +"Q8 c #B1ECD5", +"R8 c #9EBCE6", +"S8 c #B9F7CC", +"T8 c #9FBDE5", +"U8 c #8F9AF6", +"V8 c #878AFD", +"W8 c #ACD7D9", +"X8 c #BCEECB", +"Y8 c #A2BDE3", +"Z8 c #929EF3", +"`8 c #8A8EFB", +" 9 c #8A8FFA", +".9 c #96A3F1", +"+9 c #ABC8DF", +"@9 c #C9F9C8", +"#9 c #D0FFC8", +"$9 c #D2FFC7", +"%9 c #D4FFC8", +"&9 c #D5FEC9", +"*9 c #9BA6F1", +"=9 c #B4C8E2", +"-9 c #DCFFCB", +";9 c #DDFFCD", +">9 c #DEFFCD", +",9 c #FAFAFF", +"'9 c #C3C3FF", +")9 c #8B8BFF", +"!9 c #8989FF", +"~9 c #C5C5FF", +"{9 c #F3367D", +"]9 c #F6357B", +"^9 c #F53F8F", +"/9 c #F44093", +"(9 c #E54CA6", +"_9 c #B767D1", +":9 c #9C78EB", +"<9 c #8D80F8", +"[9 c #8784FE", +"}9 c #8E80F8", +"|9 c #A076E9", +"19 c #C165CE", +"29 c #ED4DAB", +"39 c #B56FE0", +"49 c #E958C1", +"59 c #B76FE1", +"69 c #977DF5", +"79 c #8A83FC", +"89 c #CD67D9", +"99 c #B474EA", +"09 c #E762D6", +"a9 c #B674EC", +"b9 c #967FF9", +"c9 c #8984FE", +"d9 c #8C83FD", +"e9 c #A17CF6", +"f9 c #D06EE7", +"g9 c #B37BF9", +"h9 c #E570F4", +"i9 c #B57CFA", +"j9 c #9682FE", +"k9 c #8984FF", +"l9 c #C678FA", +"m9 c #DE72F5", +"n9 c #9B79F1", +"o9 c #8D66CE", +"p9 c #8472E0", +"q9 c #837CF0", +"r9 c #8381F9", +"s9 c #8484FD", +"t9 c #8584FE", +"u9 c #8282FA", +"v9 c #7C7DF0", +"w9 c #7276E3", +"x9 c #636CD0", +"y9 c #4A5EB3", +"z9 c #1F5195", +"A9 c #2C65A3", +"B9 c #5576CE", +"C9 c #707FE9", +"D9 c #8F8FFF", +"E9 c #A3A3FF", +"F9 c #C8C8FF", +"G9 c #F9F9FF", +"H9 c #FEFEFF", +"I9 c #BFBFFF", +"J9 c #8686FF", +"K9 c #9EBEE7", +"L9 c #BBFECB", +"M9 c #9BB4E9", +"N9 c #8E98F6", +"O9 c #ACD7D8", +"P9 c #A5C2E2", +"Q9 c #8C91F9", +"R9 c #BEE3D3", +"S9 c #D1FFC8", +"T9 c #D6FFCA", +"U9 c #D7FFC9", +"V9 c #B0C3E4", +"W9 c #A1ADEE", +"X9 c #DDFFCC", +"Y9 c #DEFFCC", +"Z9 c #DFFFCC", +"`9 c #DFFFCD", +" 0 c #E0FFCE", +".0 c #B3B3FF", +"+0 c #A46FDA", +"@0 c #F53479", +"#0 c #F63579", +"$0 c #BC64C9", +"%0 c #8684FE", +"&0 c #907FF6", +"*0 c #DA57BA", +"=0 c #F14CAC", +"-0 c #AF72E4", +";0 c #967EF4", +">0 c #CD67D7", +",0 c #AE76EC", +"'0 c #967FF7", +")0 c #B975EE", +"!0 c #AD7CF9", +"~0 c #9581FD", +"{0 c #CA78FC", +"]0 c #A07AF3", +"^0 c #9D6ADB", +"/0 c #8781FA", +"(0 c #6F75DF", +"_0 c #526DCA", +":0 c #8485FE", +"<0 c #9191FF", +"[0 c #E3E3FF", +"}0 c #BBBBFF", +"|0 c #9DBBE8", +"10 c #8686FE", +"20 c #ADD7D7", +"30 c #A2BBE5", +"40 c #898BFC", +"50 c #C5EAD1", +"60 c #D9FFCA", +"70 c #C5E0D7", +"80 c #9297F7", +"90 c #DAF9CF", +"00 c #E0FFCD", +"a0 c #E1FFCE", +"b0 c #E2FFCE", +"c0 c #CB52AB", +"d0 c #F63477", +"e0 c #F73479", +"f0 c #F43F8D", +"g0 c #B666CE", +"h0 c #8A82FB", +"i0 c #DF54B5", +"j0 c #F34BA7", +"k0 c #CD66D5", +"l0 c #8685FE", +"m0 c #CD6EE7", +"n0 c #ED65E0", +"o0 c #8685FF", +"p0 c #CA78FA", +"q0 c #A57BF6", +"r0 c #C46DE8", +"s0 c #B36BDF", +"t0 c #917CF4", +"u0 c #7375E1", +"v0 c #3556A0", +"w0 c #5F6FD4", +"x0 c #EAEAFF", +"y0 c #E5E5FF", +"z0 c #9797FF", +"A0 c #DADAFF", +"B0 c #ABDADB", +"C0 c #8D98F7", +"D0 c #B1E6D3", +"E0 c #A4C6E0", +"F0 c #B5E9CF", +"G0 c #B7EBCD", +"H0 c #ABD3D9", +"I0 c #B5E7CF", +"J0 c #BAE4D1", +"K0 c #A2B6E8", +"L0 c #BDE2D3", +"M0 c #C4EDCE", +"N0 c #BADCD7", +"O0 c #939CF4", +"P0 c #98A2F2", +"Q0 c #DAFFCA", +"R0 c #D4F4CF", +"S0 c #8E91FA", +"T0 c #898AFD", +"U0 c #CFEAD6", +"V0 c #E3FFCE", +"W0 c #E3FFCF", +"X0 c #9494FF", +"Y0 c #D9D9FF", +"Z0 c #ECECFF", +"`0 c #C6C6FF", +" a c #A36FDB", +".a c #F73276", +"+a c #F73378", +"@a c #E93E89", +"#a c #E83E8A", +"$a c #B266CD", +"%a c #E74292", +"&a c #E74392", +"*a c #E74393", +"=a c #E74394", +"-a c #DC4DA5", +";a c #B26AD4", +">a c #DA52AF", +",a c #E44DA7", +"'a c #D457B7", +")a c #9A79EC", +"!a c #9F77E9", +"~a c #F24AA4", +"{a c #F34AA7", +"]a c #8983FC", +"^a c #BF6AD8", +"/a c #DE5CC6", +"(a c #E05CC6", +"_a c #C966D5", +":a c #DB5FCB", +"b c #8684FD", +",b c #F73172", +"'b c #F73173", +")b c #F63275", +"!b c #F63377", +"~b c #B861C4", +"{b c #937CF0", +"]b c #C561C7", +"^b c #977CF1", +"/b c #A478EE", +"(b c #DE63D5", +"_b c #C271E7", +":b c #9780FA", +"c c #D1F0D2", +",c c #D2F0D2", +"'c c #D3F0D2", +")c c #D9F7CF", +"!c c #DFFFCE", +"~c c #E1FECE", +"{c c #9DA5F2", +"]c c #CCDEDD", +"^c c #EBFFD3", +"/c c #F72E6D", +"(c c #F7306F", +"_c c #B860C2", +":c c #F5367D", +"d c #969CF5", +",d c #BDD4DE", +"'d c #CFEBD5", +")d c #D1EDD4", +"!d c #CBE4D8", +"~d c #BBCFE1", +"{d c #A4AFEE", +"]d c #8A8CFC", +"^d c #CBDFDB", +"/d c #9A9FF5", +"(d c #DBEFD7", +"_d c #DCF0D7", +":d c #DCF0D8", +"e c #D5FFC8", +",e c #B3C4E5", +"'e c #9092FA", +")e c #F1FFD6", +"!e c #F1FFD7", +"~e c #F82A66", +"{e c #F82B67", +"]e c #F82C6A", +"^e c #DD418D", +"/e c #AA6AD2", +"(e c #F63378", +"_e c #BF5CBB", +":e c #917DF2", +"f c #F1FFD5", +",f c #F2FFD6", +"'f c #F3FFD8", +")f c #F4FFD8", +"!f c #F92860", +"~f c #F82860", +"{f c #F82962", +"]f c #F72A64", +"^f c #F72A65", +"/f c #F82B66", +"(f c #F82C69", +"_f c #F73171", +":f c #F73274", +"g c #EBFFD4", +",g c #F7FFDB", +"'g c #F92357", +")g c #F92459", +"!g c #F8265B", +"~g c #F8285F", +"{g c #F82862", +"]g c #F82B64", +"^g c #F72F70", +"/g c #F53C8B", +"(g c #F34399", +"_g c #F24AA8", +":g c #F14EAD", +"h c #FA1D49", +",h c #FA1D4B", +"'h c #F91F4E", +")h c #F92151", +"!h c #F92153", +"~h c #F9255B", +"{h c #F72A63", +"]h c #F53981", +"^h c #F53B8A", +"/h c #F152B9", +"(h c #FAFFDB", +"_h c #FFFEE2", +":h c #FB1C48", +"i c #FB173D", +",i c #FB183F", +"'i c #FA1942", +")i c #FA1B48", +"!i c #FA1E4B", +"~i c #FFF9E7", +"{i c #FFFAE8", +"]i c #FC163B", +"^i c #FB173E", +"/i c #FB1940", +"(i c #FB1A42", +"_i c #FB1B47", +":i c #F92154", +"j c #FFF8EE", +",j c #FFF7EE", +"'j c #FD0F2D", +")j c #FC1233", +"!j c #FA1941", +"~j c #FB1D48", +"{j c #FA2255", +"]j c #F5387F", +"^j c #F34193", +"/j c #F3469C", +"(j c #FDFFE0", +"_j c #D9F3C4", +":j c #9FE296", +"k c #F4F6DF", +",k c #A0E099", +"'k c #E4F1D4", +")k c #9DDF97", +"!k c #71D471", +"~k c #5ACE5E", +"{k c #59CE5D", +"]k c #70D471", +"^k c #9CDF97", +"/k c #E4F0D6", +"(k c #9DDF9A", +"_k c #F1F4E5", +":k c #9DDF9B", +"l c #92DF96", +",l c #A6E5A9", +"'l c #FEFFFE", +")l c #A2E4A5", +"!l c #4ECA53", +"~l c #A07241", +"{l c #FC112E", +"]l c #8F864A", +"^l c #6BAC4F", +"/l c #7C9B4F", +"(l c #F91B45", +"_l c #F91F4B", +":l c #A37550", +"m c #57C256", +",m c #9F856E", +"'m c #D55881", +")m c #DE5186", +"!m c #D35B83", +"~m c #BA717B", +"{m c #94906F", +"]m c #67B65D", +"^m c #8B976C", +"/m c #FFFFE2", +"(m c #FFFAE5", +"_m c #FFFBE7", +":m c #97DE91", +"n c #FEF9F6", +",n c #98E19B", +"'n c #99E19D", +")n c #FBFEFB", +"!n c #99763A", +"~n c #FE061A", +"{n c #FE071B", +"]n c #FE071E", +"^n c #98793D", +"/n c #54C351", +"(n c #957E45", +"_n c #FC1133", +":n c #9A7B47", +"o c #FFFAF9", +",o c #51C651", +"'o c #E91C1C", +")o c #FF0416", +"!o c #FE0517", +"~o c #E81F21", +"{o c #908140", +"]o c #FD0A23", +"^o c #FD0B24", +"/o c #9C7641", +"(o c #FD0F2F", +"_o c #9C7744", +":o c #8C894B", +"p c #9B7F53", +",p c #839959", +"'p c #84995D", +")p c #4FC954", +"!p c #DB517C", +"~p c #DDEFD0", +"{p c #75D576", +"]p c #FEF8F2", +"^p c #A1E09E", +"/p c #A0E0A0", +"(p c #9DE19E", +"_p c #FFFCFB", +":p c #98E19C", +"

    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>`:=)=)=)=)=)=)=) <,7<8<=)=)=)=)=)=)=)9<=)=)=)=)0q&a<[#b<=)=)=)=)=)=)=)=)=)=)=)=) ", +" =)=)=)=)=)=)=)=)=)=)=)=)cO;O;h>e<'{=)=)=)=)=)=) <{)J)e!f<'{=)=)=)=)=)=)g<({i_i_=)=)=)=)h<]^W^X^Y^il'',O,),W[,[m<=<'['[o<)[)[ (^s]8{|{=)=)=);< /![|^f/%(~[p<{[][^[$] /[6{([_[:[><0{<[[[}[|[y:a^1[2[3[4[5[)/-~;~N)~<7[^)8[)~!~9[=)=)=)=)0[a[/_ 'b[=)=)=)=)c[ %__d[@%=)=)=)=)e[p-k+l+f[=)=)=)=)g[)*4~u-{%h[i[j[Z_k[l[m[n[o[=)=)=)=)4%]'F-p[8%q[r[s[P/t[u[v[w[=)=)=)=)x[0_.@S-y[o~f*z[![|^f/%(~[p<{[][^[$] ", +" L;L;e>f>g>O;A[l'O,),,)m'B[))!)~)~){)e!f!%~g!h!/{*~}:x~({({i_i_#]~^n]o]o]%=b&m;d&=}x.y.#:-}B.;}=)=)=)=)>}I.n&K.X'Y'5!r& ", +" e>O;O;k'i>',O,),,)m'')n'!)~)J)K)e!f!g!]{^{&~*~y~y~({_{@]#]m]$i>i>O,),P,m'Q,))I)~)J)K)K)L)R(g!^{&~*~w~w~y~T(i_@]#]m]n]o]{^%/-~j!q(]),~o'M}N}^~(V)O}P}Q}R}^(~]I*((t>S}M*]]+!@!w@$!y@S*x>&!<_*'E@F@z>T}U}s-'%L@+=}_O@P@Q@R@S@)!V}W}X}3(Y}]!G>{'I>Z}=)=)`} |.|[]=)=)=)=)I(+|}'|!~#1!/=@|#|:= ", +" l'O,H)m'm'')n'!)J)J)v~e!f!g!h!]{&~w~y~y~({T(@]#]#]n]o]%<%|C}^}/},|'|)|_}z_P[!|R[t<}}4[~|T[U[&/{|n(=/z~]|9^i!D~q(J_o'l!^|!,/|(|_|^,/,(,:|<|v=w=x]y]z=4;3,[|O#}|y(||G=6,U#1.^&/&4.5.C<7.`#9..$a.z!C(~*1||&2|3|4|-$E(5|6|7|8|9|0|=)=)=)=)=)=)=)=)=)a|b|1$j&3$r,c|N!d|8$e| ", +" H),)Q,))I)!)J){)e!f!R(h!h!^{*~w~y~({({@]#]#]m]n]o]o]W^X^X^+(b/b/f(#($(i(i(p_q_0:$|s_C[b:b:l<=<=/-~;~q(])P)'~R,t|7}u|v|w|{_x|y|z|A|{~b[B|^~+'/~6) %C|D|@%E|F|g+h+&%p-k+l+z>r->%,*a);')*1(u-{%,'^%6~G|H|I|J|0~A-K|L|M|N|O|P|`}Q|R|S|T|U|b%c%:'V|i~W|j~|'X|m~ ", +" ))I)!)~)J)K)e!f!g!h!&~*~*~w~({({T(i_#]Y|n]o]Z|{^%1,1;/4}-~i!,]']P)o'L_q'M_'1t')1v':;R_[;G*Y[y'!1~1J~4;{1]1v@P*:.^1D'/1U*9;^&0;a;5.H'9,8. =0.a.z!e;P@e.f.C>Y_3&(1_1:1<1Q~[1==q.R'}1b&T'v.=}x.@:#:-}Q>i&|1V'1121^= ", +" !)~){)e!f!f!g!h!&~*~w~y~({_{T(@]#]~^$V+~>u1 ", +" e!f!%~g!h!&~*~w~w~y~T(T(@]@]m]m]n]o]%<%(3};/>/B~I_,]O1P1Q1{R1~_W){(e-S1q>T1i-((k-U1Z$.!+!V1%'}<#%S*1<&!o-*!W1Y*X1-'U}s-'%L@!%@=u-v-,',!S@)!U@Y1{!Z1&=]!^!{'I>`1 25].2+2@2*##2-#$2d%%2&2T> ", +" %~g!^{&~*~w~x~({({_{#]~^m]n]o]{^{^W^X^+(b/b/f(#($(i(i(p_+_+_s_r_a:b:b:m<=<=<'['[o<)['}F[F[!}~}%|x}y}f|f|*|g|`|`|y1y141A1z1A1B1*2=2 -2o:;2>2,2+1'2)2$1;|A}]}D}/}!2L[~2N[z_P[{2R[t,']*6~(%H|y-s2t2A-K|u22%v2c~w2L+p[f~x20%y2z2c%:' ", +" }:y~y~({T(@]#]~^$~k2P)f{F~q'Q2s'N_l>v'R2R_)]G*Y[y'J*~/J~K~S2T2U2V2'&W2D'X2B@]&W*/&a;Z*T}9,J@ =0.Y2N@e;P@Q@,!g.Z23&`2 3P~&}Q~ :.3q.U~}1b&$#+3e&x.@3#3-} ", +" ({T(@]#]m]~^n]o]%<]^$3Y^+(b/c/#($($(i(p_p_+_r_$|C[a:b:m3K25161,381#1-|01'3]}^})3B_L[)|_}z_<}Q[s<2[3[!3~3N1t:&/l/m/o(o/A~9^i!;~k!O)^)z:A:t/:)D:8}{3]3f1^3|)/3(3V&W;_3:3M#Z;<3.*[3@*S#T#U#V#E]j+J=}3|3DB+33:*43k1l1g)5363733*8393O:03a3 ", +" #]Y|~^$|^}a1E}k3(}l3N[y:a^Q[R[t1j2L}9^-~;~>~']P)K_n3o3^~(p3q3r3](R}s3t3I*B|u3U1v3]]N*@!w@w3#%x3A@y>_]*'X*=%G@r-y3z3'%)%+=@=O@{%Q@A3B3#=!!W}X}3(&=C3^!D33%E3F36%6<+2G3*# ", +" n]n]Z|%4,4 '4)4!4~4=3{4]4^4-2d2K2e2h3.1/4=|91z}A}]}(4)3,|F}(}_}O[y:a^_42[n|o|R3N1J}&/l/:4>(3}L}>/-~;~>~O),~'~()<4!~~~!_W,n2d-[4}4A| 'b[|4X&+'/~#'a+C|+%d+<~f+14h+24p-=*l+-*;*>%s-q+;'s+!*~%v+,']*6~34(*44_*!'54K|;>2%I+64w2F-G- ", +" X^Y^+(b/c/c/$(g(i(i(p_+_r_s_a:a:b:l<=4>4848484 94'404a4b4c4=3;3^4H2d4>3;251,2+1@1#1z}%1G1H1D}B_K[L[M[_}G}H}Q[R[n|e4-/~||}E_V[l/n(,1f4>/-~C~D~O1P)Q1F~q'g4h4t')1v':;i4[;G*j4y'z'~/=&4;;&+!v@k4:.C'D'E'B@]&l40;a;Z*(&I@-!9.B>+=@=O@P@Q@|&m4n4o4(1 3X@<1p4 :8&T~U~k;L> ", +" +(f(f(#($(g(p_p_+_+_s_s_a:a:b:m<=<=<'[o4,4848484q4q4 r4s4t4'4K3b4u4=3v4w4x4I2>3K2e261+1@1#1j3j|{}]}^}a1E}F})|l|O[<}H}s<2[}}y4'{*d,*$H4g}h}I4f)i}J4K4m153h) ", +" c/#(g(i(p_q_+_+_C[s_a:b:b:m<=4,4>484M4q4q4q4N4N4 O4P4Q4R4'4)4a4e3f3{4S4N3H2>3o:T461,281@191z}A}k|H[a1,|k3L[U4:}g2K11[[}t/-~;~D~~~(V)X,{(Q}W4X4Y4Z4`4u3Y$M*]] 5V1%'.5y@$%T*&!&'*'X*Y*Z*`*>%+5@5)%>'}_u-{%,'R@B3)!!!#5z-3($5]!%5H>I> ", +" i(p_p_+_r_r_C[t_a:l4>4>484M4q4q4N4&5N4N4*5 =5-5;5>5Q4,5'5)5!4c4{4;3N3!5-2J2;251,2E1@1#1$101'3]}^}/}K[L[M[_}-1P[Q[I}2[m(~5s:T[t:&/l/:4,1j2{5p/i!,]k2J_o'l!^|'//|t(_|]5Y3H~:|^5v=/5x=y]T_A=z][|D=6;7;||[.}.|.F'A((54.5.(&9,_&9.<&+$a,+4+}[2_52|3|:5<5[55|}532%49| ", +" ._q_r_r_a:a:b:m4>48484M4M4q4&5q4N4|515*5*525 35455565r4758595)4a405=3S4F2a5-2b5o:e2Q371h|c5-|01A}k|^}d5E}'|(}=1G}<}a^{22[3[y4p|S[D_E_l/;(>(s|]|=~C~j!>~])^),_e5_)7}~~f5W,})g5y|h5i^{~i5j5X;k5@'6)a+8)+*l5m5[~n5h+24p-k+l+-*`*$>p+q+'*o5b)u+v+{*p5q5348~44J|!'A-r5 ", +" s_C[a:b:b:m4,4s5,484M4q4&5q4N4N4|5*5t5u5u5v5 w5x545y5z5;57594'4'5a4b4f3A5F2B5H2C5o:T45^D5E18191$101&1E5F5B_K[L[G5N[O[<}{2R[t<3[y4R3|}E_V[l/T3o(;/W[-~i!D~O)P)Q1F~q'M_T)H5I5v':;!/[;G*Y[J5K52;L5-&B']1M5k4'&C'Z[%!U*C@W*N5a;X1T}I@-! =.=+=.}O5P5Q@A3g.n4Q5R5 3:1S5 ", +" a:b:b:*<=<=<'[o4>484848484q4q4N4N4N4*5*5*5u5u5U5V5W5X5 Y5Z5`5 645.66575+6,5@6)4!4f3=3g3N3#6I2J2o:e2$6%681=|j301{}k|^}/}!2F})|&6z_<}!|s<2[}}-/k/q|t:&/K}n(2}z~L}9^C~;~k2,~^)*6B46}=6{,-6[)9};6y/U;V;a}s!W;h{>6M#N#<3b+c+,6'6T#U#V#&*j+J=Y#E/D!6I4 ", +" *<=<=3o:e261D1+1M291z};|B}<6d5E}F}(}M[:}g2H}1[2[t/-~;~>~']7[o'n3o3^(<[6O}}6](S1s3|6i-((k-u>k^16263646.5#%$%1<&!<_*'E@Y*G@H@y3J@K@L@!%4~~%56Q@,!S@)!!!W} ", +" n<'[o4J3,484M4M4q4&566N415*515*5u5t5u5U5~6~676^6869606 a6b6c6Y5Z5`5d6=5-5;5e6f6t4'5a4!4c4=3S4w4H2C5g6K251Q3,3h6#1-|;|'3<6^}/}K[L[=1_}J1A_Q[R[t4>48484M4M4M4q4N4151515*5*5u5u5v5W5~6{67686q69606r6s6 t6u6v6w6(6Y5x6 6:6.6O4r47594'4)4!4u4=3{4;3c2H2>3y6e261,3i3@1-|z}A}]}^}/}K[k3(}U4z_y:Q[s~])f{,_'__)!~~~P;W,e1A6y|z|B6 '.'j-X;C6/~`&a+8)+*d+e+m^g+h+D6p-k+l+-*;*>*p+q+'*)*t-u+v-,'p5q5 ", +" F[F[G[!}%|x}x}&|*|*|A2`|`| 1 14141A1A1B1*2B1H3D2b2b2E2&3*3&374d3d3J3J3,4,4>484M4M4q4q4&5N415N4*5u525u5U5W5W576{6]6E60606r6s6F6F6G6 H6I6J6K6/6(6L6M6x545N6z5;57594t4O6a4e3c4P6S4G2P3b5J2C151,2h|@191$1Q6G1C}D}B_K[L[M[N[J1m|!|R[t2;L*-&B'T2v@P*U6C'S*V6U*C@W*0;F@Z*T}I@W69.X6+=b.}&P@[2Y6 ", +" ~}~}x}&|x}&|*|A2g|`|x1 1y1%3z1A1B1C2*2=2H3=2b2b2E2&3&37474d3J3T5J3J3>4,48484q4q466N4N4*5*5*5*5t5u5U5W5~6{6^68686q6r6r6Z6F6`6 7 7.7 +7@7#7$7%7v6&7Y5x6*7_6=5z5;5r4=794@6)4b4u4=3S4G2c2d4>3K25161D181#1$1j|A}]}*1D}B_k3)|U4O[-7Q[s<2[3[M1s:S[t:&/l/:4r|j2]|9^i!D~>~O)A4z:;7>7:),7-6{39}0}v]'7g{(3V&y=h{>6Z&Z;h1)7c}@*!7~7$*F'7,3.9)Y#|3{7y! $.$r+]7N=~*O= ", +" x}&|&|y}f|A2g|`| 1y141%3z1B1C2C2*2=2=2D2b2b2&3&3L4*374d3J3J3J3>4,4^784q4q4q4&5N4|515*5t5u5u5U5v5W5~6^6^6q6q606r6r6s6F6G6/7(7.7_7:7 <7[7}7|71727a6/6(6L6Z535:637O4;5475767)4a477f3{4F2c2H2>3o:e261,2E1=|91$1;|]}<6a1,|K[(}M[N[y:a^s(3};/9^-~j!N)E~>_o'n3o3V4j>V,_o-*'X*Y*f[`*y3s-@5)%+=4~~% ", +" b7c7d7e7. . . f7 &|f|*|A2`|`|y1414141A1B1H3H3H3=2=2D2E2E2&3*3*374T5J3T5J3>4,4,484g7q4q4&566N415|5*52525u5U5W5~6{6^68686h706i7Z6F6`6`6(7(7.7_7:7j7j7 k7l7m7+7@7n7o7p7v6w6Y5q735r7=5z565r4f6s7'5a4L3u4=3t7;3a5I2g6;25^61D1@191$1;|G1]}*1/}u7L[)|v7G}<}Q[R[2[3[y4~|$/E_V[l/:4o(j2A~-~C~,]k!P)^)w7x7'/n!y7p!z7w'A7. . . . B7y]T_L#z]>&v!6;y(||C76,(2F'^&/&4.5.C<7.c;9.0.a.D7 ", +" E7F7. . . . . . f7 A231`| 1y14141z1G7B1C2C2H3=2D2b2&3c3&3L47474T5d3J3J3,4,4,4M4q4q4N4N4N4N415*5*525u5U5V5V576{6]6. . . . . . . . . . . . . . . H7I7J7 . . . . . . . . . . . . K7 L7M7<7l7N7}7O7P7Q7R7S7(6L6T7U7:6.665r4758567)4!4u4=3g3]4x4-2>3o:51,2D1h6@191z}'3f2^}J[,|k3(}_}:}y:H}1[[}V7~5p|S[t:U[l/;(=/o/A~>/-~;~>~O)P1r/8[<4l2W7X7Y7Z7. . . . `7V& 8X$Y;5)6) %8) >l5.8f++8h+@8p-q-#8m+n+r2,%q+;' ", +" $8. . . . . . . f7 `|y1 141A1z1B1*2*2*2=2D2b2b2&3&3&374I3T5T5J3J3>4,4848484M4M4q4&5N4N4*5*5*5u5u5v5W57676{6869696. . . . . . . . . . . . . . . J7%8&8 . . . . . . . . . . . . K7 *8=8-8;8>8l7. . . . Q7/6c6Y5Z5x5r7,8-5P47594'4'5K3e3'8=3F2N3H2)8o:K2h371E1@1#1$1;|G1C}D},|K[L[M[N[z_a^!|R[n|3[-/S[P2E_&/m3!83};/>/-~~8,]k!,~f{F~q'{8T)H5I5. . . . ]8H*y'J*^8J~U_t@/8O*k4x@C'D'V6U*V*2.(8a;[(6.I@J@ = ", +" _8. . . :8<8[8[8b7 4141A1z1B1H3H3=2D2D2b2E2c3&3&374d3d3d3J3,4>4,48484q4q4N466N4N4N4*525u5u5v5V5}87676^6E6q606|8s6. . . . . . . . . . . . . . 18&82838 . . . . . . . . . . . . K7 485868-8k778. . . . P7u688w6(6Z5T7d6=5.665r4+6,5'498!4u4=3S4F2c2C5>3K2e2,2D1i3=|-|z}A}]}*1)3,|L[)|_}O[A_{2R[2[}}-/'4>4,48484q4q4q4&5N4N415*5*5*5u5u5U5}8~6{6]6869696r6i8s6F6G6j8k8k8l8m8n8o8p8q8r8. . . s8t8u838v8 . . . . w8[8[8[8[8[8[8[8x8 y8z8A85868M7. . . . B8I6C8p7D8E8L6x6 6:6.6O4r47594t4)4a4e3=3{4F2G2-2b5F8C1h3D5+1@1F1z}A}G1^}a1,|K[L[_}:}O2<}{22[t<-/p|~|D_&/V[m/r|G8;/9^-~j!q(O)7[X[n3o3H8j>V,~_X,I8e-S1X4h-i-B|k-Y$J8]]N*.%46}<|<$%A@y>o-*!X*Y*f[-' ", +" . . . . . . . . . . K8h8L8_8M8N8O8P8Q8b2b2&3&3&3*3. . . . R8S8T8U8V8. W8q4q4N4N4N4*515*5t5u525W5X8Y8Z8`8s8 9.9+9@9s6F6`6/7(7#9$9_7%9H7H7I7&9*9. . . . =9-9v8;9>9 . . . . h8 . . . . h8,9'9L8)9!9d7~9{9y8]948. . . . . . . . . . . ^9c6/9q7_6(9_9:9<9[9}9|91929!4f3=3-3N3#6. . . . 3949596979. 89<6. . . . 9909a9b9c9d9e9f9y4~|b1U[F_l/. . . . g9h9i9j9k9. l9m9. . . . n9w'H~m6^5o9p9q9r9s9t9u9v9w9x9y9||C7}.z9F'^&A9B9C9_8M8D9E9F9G9 ", +" . . . . . . . . . . H9I9J9. . . . . . . . K9L9*3*3d3d3. . . . M9N9. . . . O9&5N4*5*5*5u5u5u5v5V5~6P9J9. . . . . . . Q9R9/7.7S9:7:7%9H7H7T9U9%8V9. . . . W9X9Y9Z9`9 0 . . . . h8 . . . . .0d7. . . . . . +0@0#0]9. . . . . . . . . . . u6a6/6(6$0%0. . . . . . . &0*0!4u4=0-3G2. . . . -0;0. . . . >0A}. . . . ,0'0. . . . . . )04[R3N1J}&/. . . . !0~0. . . . {0'~. . . . ]0W,Z7A6^0/0. . . . . . . . . (0+*d+e+#*n5_0:0. . . . . . . <0[0 ", +" . . . . . . . . . . }0. . . . . . . . . . . |0d3d3;4J3. . . . 10. . . . . 20*5*5t5u5u5u5v5~6~67630. . . . . . . . . . 4050_7:7j7H7T9J7J760607010. . . 8090`900a0a0b0 . . . . h8 . . . . . . . . . . . . . c0d0e0. . . . . . . . . . . I6$7f0g0. . . . . . . . . . h0i0j0~4f3{4. . . . %0. . . . . k0-|. . . . l0. . . . . . . . m0o|n0~|t:. . . . o0. . . . . p0,~. . . . q0r0V)s0t0. . . . . . . . . . u0v@P*v>v0w0. . . . . . . . . . )9x0 ", +" [8[8. . . . w8[8[8[8 y0. . . . z0A0K8B0C0. . . . D0J3,4,4. . . . . V8E0F0G0H0I0*5u5U5v5W5X576]686J0. . . . K0L0M0N0O0. . . P0H7H7U9U9%860&8Q0R0S0. . . T0U000a0b0V0W0W0 . . . . . . . . . . . . G9 . . . . . X0Y0Z0`0. . . . a.a+a@a#a. . . . $a%a&a*a=a@7I6-a. . . . ;a>a,a'a)a. . . !a~a{a!4u4. . . . . ]a^a/a(a_a:a#1. . . . . /-~;~. . . . Tao3V4u|[9. . . UaS1g-h-I*j-u3VaM*Wa 5Xa. . . Ya%% E7. . . . <8 ", +" . . . . h8 Za. . . `a d3T5 b. . . .bM4q4q4. . . . +b*5u5u5u5U5V5~676]686q696h7i8i8@b. . . . . . . . . . . . . #b&828u8-9X9X9$b. . . . %bb0W0xa&b&b*b=b-b . . . . . . . . . . . . G9 . . . . E9 ;b. . . >b,b'b)b!b. . . . ~b*868M7k7<7m7{b. . . . . . . . . . . . . ]bQ4,504. . . . ^bG2H2b5y6K251,2. . . . /bA}E5I[/}(b. . . . _b!|s<2[. . . . :b&/{|n(o(j6>/p/. . . . (o/A~. . . . zbP1r/()Abo0. . . . . . . BbCbDb|4X;Y;Eb. . . . . . . . . . . . . Fb ", +" . . . . h8 !9. . . 0b ,484Gb. . . V8N4*5*5. . . . HbV5~6{6^686q606Ibi8F6F6`6/7(7.7s8. . . . . . . . . . . . . JbX9>9Z9`9KbLb. . . MbNb&bya*bObgbPbQbQbRb . . . . h8 . . . . h8 h8. . . . SbTb,b'b. . . . Ub]948A8Vb68pbrb. . . . . . . . . . . . . Wbz5;575. . . . Xbf3=3;3c2H2b5g6. . . . Yb)2$101&1Zb. . . . `b:}y:P[. . . . cS[D_E_S3l/n(o(. . . . .c])P)K_'(+c@c[9. . . . . . . #c$cJ*<c>c,c'c)c`9!c 0~c{c. . . J9]c*bOb-bgbPbhbQbRb^c^c . . . . h8 . . . . h8 h8. . . . /cSb(cjb. . . . _cFa#0]948:cob9vcwc00a0a0b0xc. . . . yc-b-bgbhbQbRbzc^cAcAcBc . . . . h8 . . . . h8 h8. . . . CcDcEcFcrb. . . GcHc+aIcy8JcA8Kc. . . LcMc}7|7o7$7a6/6NcOcT7_6r7Pc. . . . Qc)4a4Rcc4g3S4x4. . . . OaSc+1@191Tc. . . . UcF}Vc_}. . . . Wc}}m(p|k/P2U[F_. . . . Xc-~;~N)Yc^)K_n3ZcV4`c d. . . . .dg-h-+d. . . @d#d ,9$d ", +" . . . . h8 y0. . . . %dA0K8&dN9. . . . *dV5~676. . . . =dr6s6F6F6`6/7(7S9S9-d%9H7H7T9U9;d. . . . >d,d'd)d!d~d{d]d. b0b0V0^dJ9. . . /d(d_d_d:d/p/;~o0zdAdBdCdDdEdFd. . . . Gdm6^5Hd. . . . IdJdzaK8KdLdMdNd. ", +" . . . . h8 Od. . . . . . . . . . . Pd~6{6^686. . . . Qd`6G6/7/7.7S9_7:7j7H7I7T9J7%86060Rd. . . . . . . . . . . . W0SdyaTd. . . . . . . . . . . . . . . 2dUd . . . . . . . . . . . . L8 . . . . h8 h8. . . . VdWdXdYdZd. . . . . . . `dFaIc]9 e. . . . . . . . . . . . D8E8.ex6. . . . +eQ4@e0498!4Rc#e. . . . ]a. . . . . . . . 89]}H1J[B_. . . . $ea^s<2[}}o|p|S[. . . . %eo/{59^. . . . . . . . . . . o0&eS;y|h5*e. . . . . . . . . . . . ", +" . . . . h8 H9K7. . . . . . . . . =e-e^6860606. . . . ;e/7(7.7_7_7j7j7>eI7U9%8%860Q03838v8,es8. . . . . . . . . 'eya*b-b. . . . . . . . . . . . . . . . )e!e . . . . . . . . . . . . L8 . . . . h8 h8. . . . ~e{e4d]e^erb. . . . . . /eHc(ee0Jc_erb. . . . . . . . . :ea6/6c6Y5. . . . 7e)]8et9. . . . . . . . . F7 ", +" . . . . h8 Z0h8L89eJ90eae30be86E696r6s6s6. . . . ce.7-d:7j7H7H7I7U9%8&8Q02838v8v8X9Y9`9deeefegeheiejekelemeOb-bgbgb. . . . . . . . . . . . . . . . neoe . . . . . . . . . . . . L8 . . . . h8 h8. . . . peqe{e{e4dreseterb. . . uembDa!bvecdwexeyezeAeBeCeDeEeFeI6o7%788&7. . . . Wb-5;5r4s485'5)5. . . . GeHeIeJeKeLeMeNe@1#1j301&1]}. . . . Oel|O[g2!|s<2[3[. . . . Pel/:4,1QeReSeTeUeVeVeWeXeYeZe`e{,-6 f]3.ft=+f@f#fD9M8!9F7$fI9%f ", +" {686E6h706r6i8F6`6 7&f(7S9_7:7%9>eI7I7I7J7%8602838u8-9X9X9>9`9 0 0b0b0W0V0W0ya*b*b*b=bPbgbQbRbRb^c^cAcBc*f=f=f-fUd;f>f)e,fneoe'f)f !f~f{f]f^f/f{e(f]eCc/c6d(cTb_f'b:fmb!bFaIc3o:e2h3.1h6=|j3z}A}k|<6D},|K[L[M[:}A_a^s/-~;~N)E~,~K_n3)~H8`c3f[6X,}6[4^_o2 ", +" q6h7r6s6i8`6`6/7(7.7$9_7j7%9H7I7U9U9%8%8Q038u8v8-9X9>9`9 0 000b0W0W0W0xaya*bOb-bgbgbPbRbRb^c^c4fAcBcBc5f=f2dUd)e)e)eneoeoe'f)f6f7f 8f9f!f0f{fpeafbfcf(fCcdfefSbTbjb'bffmbDa!bIcy8gfA8*8obM7;878|fIahfif27a6^9(6Y5x6_6jfPc-5;5kfs4. . . . lf=3S4B5O3-2J2;25^61E1M2mfz}01G1H[^}E}u7L[)|l|J1-7Q[R[t<3[p|~|D_U[&/l/:4,1;/W[-~i!D~k2,~Q1p'^|'/W3t')/]5w'H~m6 ", +" i7i8s6`6`6&f(7.7$9:7%9H7H7I7J7U9%8&82838v8v8X9;9>9`900 0a0b0V0W0xayayaOb-b-bgbPbQbQbzc^cAc4f*fBc=f-f2dUd;f;f)eneoene'f'f)fnf7fofof pfqfrfsftf{fufaf/fbfVdXdDcDc6dSbjbvf'bwf.a!bFacd(o/;/p/i!D~q(O)f{r/()<47}<)BfQ;{_S; ", +" F6G6/7/7.7S9_7:7%9H7H7J7J7%8&86038u8u8X9;9>9>9`9a0a0a0b0W0xaxaya*b*b-bgbgbhbhbRbCf^c^c*fBc5f5f2dDfUd>f)e,f,foe'fEfEfnf7f7fofFfGfGf HfIfJfKf9f~f{fLfuf/fbfVdXdMfDc6dSbTbvf'b:fmbHc+aIcy8gfA858NfL7xf78[7OfPfn7Qfa6^9c6Y5T7 6r75565. . . . Qc{ab4c4P6S4G2O3C5o:e25^D5E1Rf#1z}A}G1C}I[B_K[L[M[N[O2P[{2R[t<3[p|k/$/U[V[m/>13}Sf>/9^;~,]])7[Q1Q)N}TfUfk>)1P_ ", +" &f(7S9$9_7:7j7I7T9J7%8%860&8u8-9X9X9Y9`9`9 0a0a0VfVfW0xa&bya*b=b-bgbgbQbRbCf^cAcAc*fBc5f-f2d;f;f)eneneneoeoe)f)f7fnf7fofGfWfXfYfZf `f g.gJfKfrf!f+g{fufafbf@g4dXdDcDc#gTb$gkb%g)bHc!bIccd]9JcA8ob-8;8<7&g[7@7|7o7%7^9*gY5.eT7jf37. . . . =g@6K3L3u4A5g3G2x4)8F8K25161+1i3#1-|01&1]}D}J[,|F}~2_}G}A_{2s9Z900 0;gb0VfW0Sd&bya*b=b-b-bhbQbQbRbCf^c>g4fBc5f5f-fUd2d)e)e,fneoeoe'f)fnf7fofofFfGfYf,gYfZfZfZf 'g`f)gHf!g8fKf~g0f{g]f]g/fbf(f4dCc/c6dSb^gvf'b%gHc!b(ey8y8JcA8*868M7qbl7[7}7|7/g27K6w6(6L6T7 6(g55z5r4759467)4_ge3=3:gw4N3H2b5o:e26171/4#1F1z};|G1H1J[E}K[(}U4:}g2a^s(s|{5>/-~;~q(O)P1o'n3)_V4j> ", +" j7%9H7I7U9J7%8&8Q038v8-9X9>9Y900`9a0a0VfV0xaxaSdya*b-bgbgbhbRbRbRbzcAc4fBcBc5f=f2d2d;f;f)enene/-~i!D~k!O)9gL_x7}b ", +" I7T9J7%86060283838-9Y9Y9`9`900a0b0VfW0W0ya&byaOb=bgbgbhbQbRbCf^c4f4fBc5f-f2d2dDf;f)e)e,foeoeoe)fnfnfofofofGfGfXfZfZfZfZf[g[g}g}g0g agbg1g2g3gcg.gJfdgrf~ftf{fufafegbf4dXdYd/c6dTbjb,b5gmb)b!b(ecd]9fgA8obpbM7<7|f+7@7I6/g%7a6&7(6L6T7d6=5gg;5kf75s7@6)5!4u4hgS4B5c2-2b5K2e26171E1=|$1j|A}k|(4a1,|F}(}_}N[g2Q[s<2[t<4['<|}J}&/l/m/=/o/{59^i!;~E~])7[r/8[ ", +" %8%8&8282838X9;9>9`9`900a0a0b0b0W0Sd&bya*bObgbgbgbQbzcCfRb>g4fAc5f=f2d2dUd;f)e,f!eneoe)f)f)fnfig7fofWfGfGfYfZfZfjgjgkg[g}glgmgmgng ogpgqgrg1g2gsg g!gqfKf~gtf{fLf^f/f{e(f(fCcDc#gSbTbjb,b:ftgHc(eIccdgf:cug68-8vg>8|f}7hfwg27a6/6(6L6Z5_645.6z5;54794xg)4!4e3f3=3F2^4H2b5g6e25^D5h6yg91zg;|G1H1D},|K[L[M[:}y:a^{2R[t(,1;/>/-~;~q(k2P1o' ", +" 282838v8v8Y9Y9Y9`9a0a0a0W0W0W0yaya*b-b-b-bgbPbRbRbRbAc4fAcBcBc5f2d2dUd;f)e)e,foeoe'f)fnf7f7fofGfGfGfYfYfZfZf[gjgkgAg}g}gmgmgngngBg CgDgEgagbg1g2g`f gFgJf8fGg~f0f{fufafbfVd(fHgDc/c#gSb_fvf%g)bHcEaFay8]948Vbob}f-8<7l7+7@7I6o7p788Ig/9x6T7Jg:6.6O4r4f685@6a4!4u4hgS4G2#6I2J2K2>2,2,381=|$1z}{}<6*1/}K[L[=1l|O[<}a^Kgt<3[~5R3T[8g&/l/:4o(o/L}9^i!;~E~P) ", +" u8v8;9>9>9`900a0a0b0W0W0xa&b*bOb*b-bgbQbQbQbCf^cAcAcBcBcBc=f-f2d>f)e)e!e,foeoe)f)f7fofofofGfWfXfYfXfYfjgjg[gAgkg}g}glgmgmgLgLgMgMg NgOgPgEgQgqgrgRg`fsg gpfdgrf~g0f{fLfuf~eSg(f]eCcDc/c(cTb_f,b%g.a!bTg#0y8JcA8ug=8M7;8|f[7Ia|7ifu6a6/6(6L6x6_6375565kf[e94'4O6K3~4#eS4]4^4-2b5g6T4Q3D1h6@1F1z}A}&1UgI[,|k3(}_}N[y:K1s(3}L}=~-~j!>~ ", +" X9Y9`90000a0b0b0W0Sdxaya*b*b-b-bgbhbQbRb^cAcAc*fBc5f=f-f2dUd;f)e!enene'f'f)f)f7fofofGfFfGfXfYfZfZf[g[gAg}glg}gmgmgngngMgMgMgVgVgWg XgYgZg`gDgpgagqg1g2g`fHfFgqfrf~g~f{fLfuf h{eVd.hCc+h/cSb^gjb,blbmbHcd0Iccd]9A858}f@hk7<7[7}7Pf#h$7u6/6c6Y5$h%hd6.6-5;575+68504{ab4u4=3F2B5H2C5o:C151,2+181#1&h%1&1H[*1J[K['|M[N[G}-7{2R[2[m(~5~|T[U[&/m3n(o(z~L}p/j! ", +" 000000a0b0W0W0SdSdya*b-b-bgbPb*hRbCf^cAcAc4fBc5f=f2d2dUd)e!e,fneoeoe)f)f)f7fofofFfGfGfXf=hjgZf[g[gAg}g}gmglgngLgLgMgMgVgWg-h-h-h;h >h,hXg'hOgPg)h!hqgrg'g`fsg~hJfqfKfsf0fLf{haf/fVd4dXdCcef#g^gTb,bff)bDaEaFay8]948A8Vb=8]h<7l7[7@7^h17u688/6(6L6*7_6=5.6O4kfQ4R404{a!4u4hgv4F2^4P3d4K2/h,2.181@1j3z}A}k|^}a1,|F}(}_}O[A_Q[s<2[3[~5~3N1t:&/K}*/,1s|{5p/ ", +" a0a0b0VfW0xaya*bOb=b-bPbQbQbRb^c^c4fAcBc=f5f5f2d;f;f;f,fneoe'f)f)fnf)fofofFfGfGfGfYfZf(h[g[g}gkg}glglgmgngLgLgLgVgVgWg-h-h;h;h;h_h :h(2h;/ ", +" V0W0xaxa*b*b*b-bgbPbhbRbCf^cAcAcBcBc=f2d2dUd;f)e,fneneoeoe)f)f7f7f7fofWfGfXfYfYfZfZf[g[g[g}g}gmgngmgLgLgMgMgVgWgWg-h-h;h_h_h3h3h4h 5h6h>h2$6E1i3#191;|A}k|^}/}K[F}M[v7z_<}a^s<[}3[-/~3D_%/V[l/;(=/ ", +" Sdya*bObObgbgbQbQbzczcAcAcBcBcBc=f=f;f;f)e)e!enene'f)fEfnf7fofFfWfFfGfXfXfYfZf(h[gkgAg}gmgmgmgngngLgMgMgVgWg-h-h;h;h_h_h3h4h4hfhgh hhihjhkh3o:T461uh/4@191z}A}k|*1a1,|F}vh=1N[g2m|1[R[th2$6+1@1#1$101G1<6*1/}K['|M[l|O[A_!|s<2[m(-/N1T[E_&/ ", +" -bgbhb*hRbRbCf4f4fBc5f=f-fUdUd;f)e,fneneoe)f)f6fnfofFfFfGfGfYfYfZfZf(hjgkg}g}glglgKhLgLgMgLgMgVgVgLh;h;h;h_h3h3h3hfhfhMhghghNhwhxh OhyhPhQhRh6hBhf;fne,foe)f)f)f)fnfofofWfGfGfXfZfZfZfjgjgAgAglgmg}gngngngLgMgMg-iLhWg-h;h;h_h_h_h4h4h4hfhghNhwhNhxh`hZhZh;i;i >i,i.iOh'iPhihRh)ikh3K251Q3E1i3c591;|>|]}D}/}K[F})|_}-1P[Q[R[[}3[y4 ", +" 4fBcBc5f2d2d;fUd)e,f,fneoe'f)f)f6fofofFfGfXfXfYfXfZf[gZfkgAg}glg}gmgngmgngLgVgVgWgWg-h-h;h_h3h_h3h4hfh4hghghwhxhxhZhxhZh`h`h~i~i{i ]i>i^i,i/i(iPhhhRh_i:h>h,hXgYg$i`gEgag:irg1gDh)g gpfVhGg~g0f{f8[i}7H6ifu6a6&7(6L6x6d645,8z5r47594'404_gu4c4g3F2^4-2b5o:T4Yh,381#191z}A}]}*1a1u7F}(}M[O[<}a^{22[3[ ", +" 5f5f2d2d;f;f)e,fneneoe'f)f7f7fofofofGfXfYfYfZfZfZf[gAg[g}g0gmgmgngngngLgMgMg-h-h}i-h_h_h_h_h3h4hghghghwhNhxhZhxhZhZh;i`h;i;i~i{i|i 1i2i3i>i4i,iyh(i@iAh5iBh6i,hXgTh'hOg)h%i0hqg1g2g)gHf!gdgrf~g~f{fLfuf h{eVd4dCc5dEcSb^g_f'blbmbDa!bIcy8JcA858NfM7;8<7|f+77iI6J6u6D8c6Y5x6`5:6y5z5;575+6s704K3e3f3{4-3G2H2b5J2e251$6E181c5-|01G1k|D}E}K[L[(}l|g2a^!|R[ ", +" 2dUd;f)e,f,foene'f)fnfnfnfofFfGfGfXfYfZfZf(h[g[g}g}glg}gmgmgngLgVgLgMgWgWgWg-h;h_h_h4h4h4h4hghghghghxhxhZhxhZh`h;i;i;i8i{i{i|i|i|i 9i0iai]i>ibi,i+i'iPhcidi5hkhi i.iyhPhniAh5iBh>h!iXgNgZgPgEg!hoiCh1g3gsg gpiqfKfsfqi0fibi i+iDi@iihdi6h6ii^i.iOi'izhnidi_iBh3o:5161D5h6M291z}A}&1H1a1E}F}(} ", +" )fnf7fofFfWfGfYfXfYfZfZf[g[g[g}g}glgmgngngngLgLgVgWg-h-h-h_h;h_h_h4hfh4hfhghwhghxhxhZhxh`h`h;i;i;i;i8i8i8i|iiiii|iiiwijiwiwiQiIiRi SiJiKiLikiMiNiaiaiTi^i4i.iUiDi@iAhdi:h>hViXgYg'hPgDgpg:iCh1g2g)gWiFgpfXi~g~f{g{fuf/fbfVdHgMf5dEcSbTb_f,b%gmbHcEaIccdgf:c58obL7vg>8[7IaPfwgo7a688w6Y5Z5x545Pcz5;575Yis704K3b4f3g3S4ZiH2b5>3K251,2E1yg)2$101G1<6*1/}u7 ", +" 7fofWfGfXfGfYfYfZf[g[g[g[g}g}glgmgLgLgLgVgMg-i-hWg;h;h_h3h4h4h4hfh4hghghghxhxhxhxhZhZh;i;i;i~i8i{i8i|i|i|iiijijiwiwiwiIiIiIiQiRi`i j.j+j@jKiLi#jzi$j%j3iTi^i.i+iyhzhnidi6hkh&j!i*jYgOgog)h=jqgrg'g`fcg gpfdgGg~gtfLfufafeg{e4dXdCcDc#gSbjbvf'bmbHh-j(eIc]9A8dh68}f-8qbl7[7@7I617;j88c6(6L6x6d6=5gg65r4+657@6)5!4~4hg:gG2x4P3d4o:e2h3,3h6Rf91z}A}]}*1/} ", +" GfGfGfXfZfZfjgjg[gkg}g}g0gmgngLgLgLgMgVgWgVg-h-h_h_h_h3h4h3hfhghghwhghxhxh`h`h`h`h;i;i;i8i{i8i|i|i|ihi|iwiwijiwiwiIiIiIiRiRi>j,j>j 'j j.jSiJi)jKiyi9i1i$j2i3i>i,i+i!jPh@idi6h~j8m7}7hf/gJ6a688c6^jw5 6:655/j;575R4'4)4a4~4c4{4;3G2H2)8g6C151,2E1Rf91z}Q6&1C} ", +" XfYfjgZfZfkg[gAg}glglgngmgngBg(jVgVgWgWg-h-h;h_h_j:jj6j7j 8j9j0j.jajbjcjKidjMiej1iBi2i^i^ifj+i'i@iihRh6h6iVi7hNgei9hPg)hagqg1g'g`fsg~hJfahgjqitfhjufaf{eSgbhXd5dEcijjj$g,b:f)bHc!bFacd]9fg*8ob@hkjljljmjnj#717%7b6w6(6L6T7U7=5z5ojkf=7t4~aa4!4f3=3S4]4a5-2>3;251$6+1i3#1-|01&1 ", +" Zfjg[g[gkg}g}g}gmgmgngBgMgMgVgWg-h-h-h;h_hpjqjrjsjsjsjsjsjsjsjsjsjsjtj;i;i`h{i~i8i8i8i|i|iii|iiijiwiwiIiIiIiQi`iRiRi>j,j,j7j>j7j6j uj8jvjwj.jSi+j@j)jxjyjzi1i2i3izj^i,i+i!jPhAjdi6hBh>h!iXgei$i`gpg%iqgrgBj`fCjHfJfqfKfDjnh{fj`i,j,j>j6j6jGjHjGj IjJjKjLj9jMj.j+jJi)jLiNjMi1iBi]iCi>i,i/iyhyhPhih5i:h6iVi!iOj'h`g)hpgqgrg1g'g`fCjFgqfVhGg~f0fLfuf h{e4dHgCcDc6dSbjjjbkb%gPj!b(eIcy8JcA8sjsjsjsjlj[7+7@7n7$7a688w6/9M6_6jf=5-5;5r4f6t4O6K3e3u4=3S47gH2d4o:K251,2E1@1#1 ", +" }glg0gmgmgngMgMgVg-iVg-h-h-h;hpj_h3h4h3hQjRjsjsjsjsjSjTjUjVjWjXjYjZjsj8i8i|i|iiiiiiiiiwiwiwiIiIiRiRi`iRi`iRi>j>j6j7j6jHj7jGj`j k.k +kIj@kJj#k$k0j%k&k+j)jKidjyjej1i%j2izjbi,i+i(i@iQhRh6h6i261.1yg ", +" mgmgmgngMgMgVgVgWgWgWg;h_h3h3h4h3h4hfh*k=ksjsjsj-k;kZhZh`h;i~i{i8i>k,k|ihiji|ijiii'k)k!k~k|j{k]k^k/k>jRi,j>j,j6jsjsjsjsj(k_k:khlaksjsjsjsjsjsj,l 'l)lRjsjsjsjsjsjsjsj!l~l{l.j&k@j)jsjsjsjsj]l^lsjsjsjsjsjsj/l(ldi6h>h3K2 ", +" VgWgWg;h;h_hpj3h3h4h4hghghxhghwhxhxhZholsjsjsjpl{i8i8i|i|i|iji|iiiHijiwiwiwiqlsjsjsjsjrlsltlulmsjsjsj,m'm)m!m~m{m]m^m(g=5z5;57594,504K3b4c4=3S4N3#6Jh ", +" -h/m_h;h_h_h4hfh4hfhghghxhxhZh(mZh`h_m|jsjsjsj:m8i8i|iiiiiiiwijiwinsjsjsjsj,n sjsjsjsj8k sjsjsjsj'n )nsjsjsjsj0m bksjsjsj!n~n{nMm]ndm^nsjsjsj/nIjJjKjsjsjsjsj(n@j_nLiLi:nsjsjsjsj>i,i.iBksjsjsjsjsjsjsjsjsjsjsjsjsjjRi>j7j7j6jbksjsjsj7n`j k k.k2m=nsjsjsjZj-n-n4msjsjsjsj8n9n9n9n9nsjsjsjsj8k sjsjsjsj8k sjsjsjsj'n )nsjsjsjsj0m bksjsjsj0nan~nbnMmcn^nsjsjsj/ndnen@ksjsjsjsjfngn+j@jUmhnsjsjsjsj3i>iinjnsjsjsjsjsjsjsjsjsjsjsjsjsjkn%iqgrgsjsjsjsjlnqf9fmh0fmnsjsjsjsjVdXdDcDcsjsjsjsjnn%g)b!bIccdy848onpnumsjsjsjsjsjsjsjqnrn/6(6Y5T7U745.6O4r4Q485'4)4!4u4 ", +" ghghNhxhxhZh(m;i;i;i~i{i{i8i|i|i|i|ijisnsjsjsjsjtnIiIi`i`i>jRi>j6j>j6j7j6j7jxmsjsjsjun2m2m2mvn2mwnsjsjsjxn-nDmEmsjsjsjsj8nynznynynsjsjsjsj8k sjsjsjsj8k sjsjsjsjIm FlsjsjsjsjJm KmsjsjsjAnBnCnanbnMmDnsjsjsjEnFnGn+ksjsjsjsjHn j.jSi+jhnsjsjsjsjai2iCiInsjsjsjJnKnLnMnMnNnOnPnQnRnSnogpg0hsjsjsjsjTnIfVhrf9fUnsjsjsjsj{eVd4dCcsjsjsjsjVnWn%g)b!bd0Iccdgf48XnYnZn`n osjsjsjsj.ou6+o(6/9M6x5r7=5z5;575+6t4@o{a ", +" xhxhZhxhZh`h;i~i;i8i8i8i|i|ihihijijiwi#o$osjsjsj-k%oRi`i`i,j,j7j,j&o$l6jGjGj*osjsjsj}m=ovnBmBm=o}msjsjsj-oDm;o;osjsjsjsj8nyn>o>o>osjsjsjsj8k sjsjsjsj8k sjsjsjsjbk9m 0msjsjsjsj7k amsjsjsj,o'o)o!oCn~obmsjsjsj{ofm]o^osjsjsjsj/oOl'j(oSi_osjsjsjsjMi$jai:osjsjsjooopoposjsjsjsj8k sjsjsjsj8k sjsjsjsjsjzlElfkqo4ksjsjsjro'l 7ksjsjsjsjsotouovowosjsjsjsjxoyoPm]osjsjsjsjzoKjAowj0jBosjsjsjsjyj9iCoDosjsjsjsjEoFoGoHoUlIoJoKosjXgTh$iPgsjsjsjsjLoDh)gHfpf2osjsjsjsjMo]f/fbfNosjsjsjOoPoQoRoSo:fHc!lToUoVoWoXoYoZosjsjsjsj`o|7I6Qfa6/6(6Y5x6 6JgN6/j;5[e94 ", +" `h;i;i{i{i8i|i|i|i|iiijiiijiwiwiwiIiIiRiIi p}msjsjsjsjsjsjsjsjsjsjsjsj`j2m2m.k.psjsjsjsjsjsjsjsjsjsjsj+p;o9n9nynsjsjsjsjnooo@p@p#psjsjsjsj8k sjsjsjsj8k sjsjsjsjbksjsjsjsjsjsjsjsj$p %psjsjsjsjsjsjsjsjsjsjsj&pNm*pOm=psjsjsjsj-p@kJj8jwjHnsjsjsjsjKiyjMiNi;psjsjsjsjsjsjsjsjsjsjsjsjp1g'g`fsglnsjsjsjsj{fnynyn>o>osjsjsjsj(p@p#p_p#psjsjsjsj8k sjsjsjsj8k sjsjsjsj:ppqgrg1g`fTnsjsjsjsj~fqi{fuf9pBksjsjsjsjsjsj0pTb&isjsjsjsjsjsjsjsjsjsjUkap<7&g[7}7|7n7C8a6&7(6L6*7U7:6y565 ", +" 8i8i|i|ijiiiiiHiwijiwiwiIiIiRi`iRi>j`i>j,j,j7j7jbpcpdpep4k|jfpgphpipjpBmBmBm-n-n-nkplpmpnp-knpwl+popynznynzn>o>osjsjsjsjppqpqpqprpsjsjsjsj8k sjsjsjsj8k sjsjsjsj8ksptpup;lvpwpxp fkypzpApBpApCpDpEpFpGpHpbnMmNmsjsjsjsjIpdn+k4pJpKpsjsjsjsj+jJi)jxjyi9iLpMpNpOpjntkPpQpRpSp(lRhBhjRi,j>j7j7j7j7j6jGjGj k k k`j.kBmBm2mBm*q-n-n4m-n4m4m4mEm;o9n9nyn=qynyn>o>o@poopo@p#p@p_pqpqprprpqp-q sjsjsjsj8k ;q>q;q>q,q'q)q!q~qFp{qGp~n{n]q^qdm/q(qFn_q:qIj4pJj#k$kwj%k&kJi_nxiyii4i}q!jDiPhQh5iBh>h>h,hXgei$iPgpg0hqgrg'g`fsg~hpfdg9f~ftfhjpe/f/fVd(f.h5d/c6d$gjbvf5gmb.aEaIcIc]9A8A8ugpb;8vg|f[7}7H6J6u6K6c6E8q7*7U7 ", +" ji|iHijiwiHiwiwiIi`iIi`iRi,j>j6j>j6jGj6jHjGjHj k`j.k k.kvnvnBmBmBm-n-n-n4m;o4mEmEm9n;o9n9n9nyn|q>o>o>ooo@p#p#p#p_p_p_pqprp-q-q1q2q sjsjsjsj8k ;q;q;q;q;q3q4q5q6q7q8q9qCn~nbn1p0qdmaq(qFn_qGn+kJpJj5pAo'j0jgnnk@j)jLibq9i$jBi2iCi^i,ifjyhzhniRh5h~j>hViXgYgOgPg)h!hqgChRgDh)g g!gdgrf9ftf{fj,j6j6jHjGjGj k k`j`j.k2m2m2m2mBmBm-n-n4m4m4mEmEm;o;o;o9n=qyn9nyn>o>ooopooo@p_p#p#p_pqpqprp-q-q-q1q$]$]$] sjsjsjsj8k ;q;q;q;q;q;q>q4q'qdq6q~qBnCnan~neqMmOmem=pfqgqGn+k@kJp5p#k'jhq.j+j+j)jxiLiMizi1iBi3i>ibi.i!j(iPhcidijh&jj>j,j6j6j7jGjGj`j`j`j k.k.k2m.kBmBm-n-n4m4m4mDmEm4m;o;o9n=q9n9nzn>o>ooooopo@p@p@p_p_pqpqpqpqp-q$]-q$]kqkqkq sjsjsjsj8k ;q;q;q;q>q>q;q;q,q'qlq)qmqFpnqCnan{nMm0qdm/qfm]ooq+kIj4ppq8jLjwj jgnnk@jxiLiyiziziai2i>i^i.iqq'iDiciAhjhBhj,j,j7j6jHj7jGjHj`j k k`j.k2m.kBmBmBmBm-n4m-n4m4mDm;o;o9n9n9nynznzn>o>o>o>o>o@p@p@p_p_prprpqp-q-q2q$]$]kqkq sjsjsjsj8k ;q;q;q;q;q;q;q>q>q>q,quqvq!qwqBnCnanbnMmNmcnyo(qfm^oGn:q@k4ppq#kwj0jxq&k@j)jxjyi9i$jai2iTi^i,i.i!jPhAjRh5i6h>hViXgThEi$i)h%ibgCh1g2g)g gpfJfdgmh~f{fj>j,j7jGj7jHjHjGj k`j k`j.k2mBmvn-nBm-n4m-n4m;oDm;oEm;o9n9n=qznzn|qznoopo@ppo#p@p_p_p_pqprp-q-q2q2q$]$]$] ;q;q;q;q;q;q;q;q;q;q>q,qyq)q!q8q)ozqAqBq{nMm^qaq=pCqFn_qdnDqJjSm8jOlMj j&k+j)jEqki#jej1i]iCi>i^i,i!jyhniihdiBh>hj,j>j,j6j6jGjGj k k k`j2m.kBm2mvnvnBm*q-nDmDm4m;oEm;o;o;o9n9n9n9nznzn>o>o>o@p@p#p#p_pqp_pqp-qrp-q-q-q$]kqGq ;q;q;q;q;q;q;q;q;q;q;q;q3q,qlqHq!qFpIqCnBqbneqNmOmem(qFnJqGn:q4pJj8j#kwj(oSi+j@j)jLiyiMi1iai]iKq^i,i/iyhPh@iRh6h:h>h!iXgNgOg`gEg%ibgrg'g`fsg gpiqfKfmhqiLqLfuf hcf(fXdCc5d/c#gTbGh5g%gDa!b+a#0]9JcA858ob-8xfFq[7tq*i ", +" ,j6j6j6j6jGj`jHj k k`j2m.k2mvn-n-nBm-n4m4m4mDm;o;o9n9n9n9nzn9n>o>o>o>ooo@ppo#p#p_pqpqp_pqprp-q-q2q$]$]$] ;q>q;q;q;q;q;q>q;q;q;q;q;q;qMqNqdq7qOqBn!oAqbnMmNmPq/qemfm^o^o:q4pJjKjvjQq'j.jRq+j_nKiLiMiej1i2iKqTi^i.i+iyhPhih5h6hSqo>o>opo>o@p#pUq_pVqqpqpWq-qrp$]1q$]$]$]Gq ;q;q;q;q;q;q;q;q>q>q;q;q;q;q;q,qXqvqHq~q)oBnCnBq{nMm]ndm/qYqFn_qGnIj4puj8j#kZq j.j+jJiKixibqMiziBi3iCi^i.i!j'izhhhih6h>h&j!iShNg'h`gUh!hqg`q1g`f)g.g!g8fKf!ftf{gLf h/fSgbh.hCcDc#g^gjbjb'b:f.a!bFacdy8JcA8qh=8M7<7<7 ", +" ;q;q;q;q;q;q;q;q;q;q;q;q>q;q;q;q>q,q5qdq!q rFpCnAqbnMm]q.raq(qfm^oGn:qIjJj5p8jQq0jxqSi@j)jEqkiMiejai2iCi>i,ifjyh(ini+r5iBh>hhandler ); + $obj = new ReflectionObject( $this->handler ); + echo "\n--- Handler reflection object ---\n"; + var_dump( $obj ); + $atts = $obj->getProperties(); + echo "\n--- Handler reflection attribute objects ---\n"; + var_dump( $atts ); + echo "\n--- Handler reflection has property activeReference ---\n"; + var_dump( $obj->hasProperty( "activeReference" ) ); + echo "\n--- Now trying ->getProperty( activeReference ) ---\n"; + $att = $obj->getProperty( "activeReference" ); + echo "\n--- Handler reflection attribute object for activeReference ---\n"; + var_dump( $att ); + $activeReference = $this->readAttribute( $this->handler, "activeReference" ); + $references = $this->readAttribute( $this->handler, "references" ); + */ + $handlerArr = ( array) $this->handler; + $reference = $handlerArr["\0ezcImageMethodcallHandler\0activeReference"]; + $referenceData = $handlerArr["\0ezcImageMethodcallHandler\0references"][$reference]; + return $referenceData["resource"]; + } + + public static function suite() + { + return new PHPUnit_Framework_TestSuite( "ezcImageConversionFiltersGdTest" ); + } + + protected function setUp() + { + try + { + $this->handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + } + catch ( Exception $e ) + { + $this->markTestSkipped( $e->getMessage() ); + } + $this->imageReference = $this->handler->load( $this->testFiles["jpeg"] ); + } + + protected function tearDown() + { + unset( $this->handler ); + } + + public function testScaleBoth() + { + $this->handler->scale( 500, 500, ezcImageGeometryFilters::SCALE_BOTH ); + $this->assertEquals( + 500, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 377, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleDown_do() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scale( 500, 2, ezcImageGeometryFilters::SCALE_DOWN ); + $this->assertEquals( + 3, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 2, + imagesy( $this->getActiveResource() ), + "Height of scaled image incorrect." + ); + } + + public function testScaleDown_dont() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scale( 500, 200, ezcImageGeometryFilters::SCALE_DOWN ); + $this->assertEquals( + 150, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 113, + imagesy( $this->getActiveResource() ), + "Height of scaled image incorrect." + ); + } + + public function testScaleUp_do() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scale( 500, 300, ezcImageGeometryFilters::SCALE_UP ); + $this->assertEquals( + 398, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 300, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleUp_dont() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scale( 500, 2, ezcImageGeometryFilters::SCALE_UP ); + $this->assertEquals( + 150, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + $oldDim["y"], + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleFailureInvalidParam() + { + try + { + $this->handler->scale( 500, 2, 23 ); + $this->fail( 'Exception not throwen on invalid scale direction.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scale( -23, 2, ezcImageGeometryFilters::SCALE_UP ); + $this->fail( 'Exception not throwen on invalid scale direction.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scale( 500, -23, ezcImageGeometryFilters::SCALE_UP ); + $this->fail( 'Exception not throwen on invalid scale direction.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScaleWidthBoth() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleWidth( 50, ezcImageGeometryFilters::SCALE_BOTH ); + $this->assertEquals( + 50, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 37, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleWidthUp_1() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleWidth( 50, ezcImageGeometryFilters::SCALE_UP ); + $this->assertEquals( + 150, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 113, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleWidthUp_2() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleWidth( 300, ezcImageGeometryFilters::SCALE_UP ); + $this->assertEquals( + 300, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 226, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleWidthDown_1() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleWidth( 300, ezcImageGeometryFilters::SCALE_DOWN ); + $this->assertEquals( + 150, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 113, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleWidthDown_2() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleWidth( 50, ezcImageGeometryFilters::SCALE_DOWN ); + $this->assertEquals( + 50, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 38, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleWidthFailureInvalidParam() + { + try + { + $this->handler->scaleWidth( 'foo', ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleWidth( -23, ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleWidth( 42, 23 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScaleHeightBoth() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleHeight( 50, ezcImageGeometryFilters::SCALE_BOTH ); + $this->assertEquals( + 66, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 50, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleHeightUp_1() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleHeight( 226, ezcImageGeometryFilters::SCALE_UP ); + $this->assertEquals( + 300, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 226, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleHeightUp_2() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleHeight( 50, ezcImageGeometryFilters::SCALE_UP ); + $this->assertEquals( + 150, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 113, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleHeightDown_1() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleHeight( 300, ezcImageGeometryFilters::SCALE_DOWN ); + $this->assertEquals( + 150, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 113, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleHeightDown_2() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleHeight( 50, ezcImageGeometryFilters::SCALE_DOWN ); + $this->assertEquals( + 66, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 50, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleHeightFailureInvalidParam() + { + try + { + $this->handler->scaleHeight( 'foo', ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleHeight( -23, ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleHeight( 42, 23 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScalePercent_1() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scalePercent( 50, 50 ); + $this->assertEquals( + 75, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 57, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScalePercent_2() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scalePercent( 200, 200 ); + $this->assertEquals( + 300, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 226, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScalePercentFailureInvalidParam() + { + try + { + $this->handler->scalePercent( -23, 100 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scalePercent( 100, -23 ); + $this->fail( 'Exception not throwen on invalid height.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scalePercent( -23, -23 ); + $this->fail( 'Exception not throwen on invalid width and height.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScaleExact_1() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleExact( 200, 200 ); + $this->assertEquals( + 200, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 200, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleExact_2() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleExact( 10, 200 ); + $this->assertEquals( + 10, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 200, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleExact_3() + { + $oldDim = array( + "x" => imagesx( $this->getActiveResource() ), + "y" => imagesy( $this->getActiveResource() ), + ); + $this->handler->scaleExact( 200, 10 ); + $this->assertEquals( + 200, + imagesx( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + $this->assertEquals( + 10, + imagesy( $this->getActiveResource() ), + "Width of scaled image incorrect." + ); + } + + public function testScaleExactFailureInvalidParam() + { + try + { + $this->handler->scaleExact( -23, 100 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleExact( 100, -23 ); + $this->fail( 'Exception not throwen on invalid height.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleExact( -23, -23 ); + $this->fail( 'Exception not throwen on invalid width and height.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScaleTransparent() + { + $ref = $this->handler->load( dirname( __FILE__ ) . "/data/watermark.png" ); + $this->handler->scale( 80, 80 ); + $this->handler->save( $ref, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 10000 + ); + } + + public function testCrop_1() + { + $this->handler->crop( 50, 38, 50, 37 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCrop_2() + { + $this->handler->crop( 100, 75, -50, -37 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCrop_3() + { + $this->handler->crop( 50, 75, 250, 38 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCrop_4() + { + $this->handler->crop( 50, 75, 38, 250 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCrop_0_Offset() + { + $this->handler->crop( 0, 0, 10, 10 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropTransparent() + { + $ref = $this->handler->load( dirname( __FILE__ ) . "/data/watermark.png" ); + $this->handler->crop( 20, 0, 10, 5 ); + $this->handler->save( $ref, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropNegativeOffset_1() + { + $this->handler->crop( -100, -100, 50, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropNegativeOffset_2() + { + $this->handler->crop( -50, -50, 50, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropNegativeOffset_3() + { + $this->handler->crop( -50, -50, -50, -50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropFailureInvalidParams() + { + try + { + $this->handler->crop( 'foo', 23, 23, 23 ); + $this->fail( 'Exception not thrown on crop with invalid x coordinate.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->crop( 23, 'foo', 23, 23 ); + $this->fail( 'Exception not thrown on crop with invalid y coordinate.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->crop( 23, 23, 'foo', 23 ); + $this->fail( 'Exception not thrown on crop with invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->crop( 23, 23, 23, 'foo' ); + $this->fail( 'Exception not thrown on crop with invalid height.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testColorspaceGrey() + { + $this->handler->colorspace( ezcImageColorspaceFilters::COLORSPACE_GREY ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testColorspaceMonochrome() + { + $this->handler->colorspace( ezcImageColorspaceFilters::COLORSPACE_MONOCHROME ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testColorspaceSepia() + { + $this->handler->colorspace( ezcImageColorspaceFilters::COLORSPACE_SEPIA ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testColorspaceFailureInvalidParam() + { + try + { + $this->handler->colorspace( 23 ); + $this->fail( 'Exception not thrown on invalid colorspace.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testWatermarkAbsoluteNoScale() + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", 100, 80 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testWatermarkAbsoluteScale() + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", 10, 10, 130, 93 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 500 + ); + } + + public function testWatermarkAbsoluteNoScaleNegativeOffset() + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -50, -33 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + strtr( $this->getReferencePath(), array( "NegativeOffset" => "" ) ), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testWatermarkPercentNoScale() + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 10, 90 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testWatermarkAbsoluteFailureInvalidParam() + { + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/foo.png", -140, -103, 130, 93 ); + $this->fail( 'Exception not throwen on invalid watermark file.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", 'foo', -103, 130, 93 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -140, 'foo', 130, 93 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -140, -103, 'foo', 93 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -140, -103, 130, 'foo' ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testWatermarkPercentScale() + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 80, 80, 20 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 450 + ); + } + + public function testWatermarkPercentFailureInvalidParam() + { + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/foo.png", 80, 80, 20 ); + $this->fail( 'Exception not throwen on invalid watermark file.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", -80, 80, 20 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 80, -80, 20 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 80, 80, -20 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testCropThumbnailVertical() + { + $this->handler->croppedThumbnail( 50, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropThumbnailHorizontal() + { + $this->handler->croppedThumbnail( 100, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCroppedThumbnailFailures() + { + try + { + $this->handler->croppedThumbnail( -10, 50 ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->croppedThumbnail( "foo", 50 ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->croppedThumbnail( 50, -10 ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->croppedThumbnail( 50, false ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testFillThumbnailVertical() + { + $this->handler->filledThumbnail( 50, 50, array( 255, 0, 0 ) ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testFillThumbnailHorizontal() + { + $this->handler->filledThumbnail( 100, 50, array( 255, 0, 0 ) ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testFillThumbnailTooLargeColorArray() + { + $this->handler->filledThumbnail( 100, 50, array( 255, 0, 0, 400, 500, 600 ) ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testFilledThumbnailFailures() + { + try + { + $this->handler->filledThumbnail( -10, 50, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( "foo", 50, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, -10, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, false, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, 50, array( 255, false, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, 50, array( "bar", 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/filtersshell_test.php b/include/ezcomponents/ImageConversion/tests/filtersshell_test.php new file mode 100644 index 000000000..7c6074c58 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/filtersshell_test.php @@ -0,0 +1,1089 @@ +handler; + return $handlerArr["\0ezcImageMethodcallHandler\0activeReference"]; + } + + public static function suite() + { + return new PHPUnit_Framework_TestSuite( "ezcImageConversionFiltersShellTest" ); + } + + protected function setUp() + { + try + { + $this->handler = new ezcImageImagemagickHandler( ezcImageGdBaseHandler::defaultSettings() ); + } + catch ( Exception $e ) + { + $this->markTestSkipped( $e->getMessage() ); + } + $this->imageReference = $this->handler->load( $this->testFiles['jpeg'] ); + } + + protected function tearDown() + { + unset( $this->handler ); + } + + public function testScale() + { + $this->handler->scale( 500, 500, ezcImageGeometryFilters::SCALE_BOTH ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 250 + ); + } + + public function testScaleDown_do() + { + $this->handler->scale( 500, 2, ezcImageGeometryFilters::SCALE_DOWN ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 450 + ); + } + + public function testScaleDown_dont() + { + $this->handler->scale( 500, 500, ezcImageGeometryFilters::SCALE_DOWN ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testScaleUp_do() + { + $this->handler->scale( 500, 500, ezcImageGeometryFilters::SCALE_UP ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 250 + ); + } + + public function testScaleUp_dont() + { + $this->handler->scale( 2, 2, ezcImageGeometryFilters::SCALE_UP ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testScaleFailureInvalidParam() + { + try + { + $this->handler->scale( 500, 2, 23 ); + $this->fail( 'Exception not throwen on invalid scale direction.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scale( -23, 2, ezcImageGeometryFilters::SCALE_UP ); + $this->fail( 'Exception not throwen on invalid scale direction.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scale( 500, -23, ezcImageGeometryFilters::SCALE_UP ); + $this->fail( 'Exception not throwen on invalid scale direction.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScaleWidthBoth() + { + $this->handler->scale( 2, 2, ezcImageGeometryFilters::SCALE_UP ); + $this->handler->scaleWidth( 50, ezcImageGeometryFilters::SCALE_BOTH ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 450 + ); + } + + public function testScaleWidthUp_1() + { + $this->handler->scaleWidth( 50, ezcImageGeometryFilters::SCALE_UP ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testScaleWidthUp_2() + { + $this->handler->scaleWidth( 300, ezcImageGeometryFilters::SCALE_UP ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 250 + ); + } + + public function testScaleWidthDown_1() + { + $this->handler->scaleWidth( 300, ezcImageGeometryFilters::SCALE_DOWN ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testScaleWidthDown_2() + { + $this->handler->scaleWidth( 50, ezcImageGeometryFilters::SCALE_DOWN ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 450 + ); + } + + public function testScaleWidthFailureInvalidParam() + { + try + { + $this->handler->scaleWidth( 'foo', ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleWidth( -23, ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleWidth( 42, 23 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScaleHeightUp_1() + { + $this->handler->scaleHeight( 300, ezcImageGeometryFilters::SCALE_UP ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 250 + ); + } + + public function testScaleHeightUp_2() + { + $this->handler->scaleHeight( 300, ezcImageGeometryFilters::SCALE_DOWN ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testScaleHeightDown_1() + { + $this->handler->scaleHeight( 30, ezcImageGeometryFilters::SCALE_UP ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testScaleHeightDown_2() + { + $this->handler->scaleHeight( 30, ezcImageGeometryFilters::SCALE_DOWN ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 550 + ); + } + + public function testScaleHeightFailureInvalidParam() + { + try + { + $this->handler->scaleHeight( 'foo', ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleHeight( -23, ezcImageGeometryFilters::SCALE_DOWN ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleHeight( 42, 23 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScalePercent_1() + { + $this->handler->scalePercent( 50, 50 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 500 + ); + } + + public function testScalePercentFailureInvalidParam() + { + try + { + $this->handler->scalePercent( -23, 100 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scalePercent( 100, -23 ); + $this->fail( 'Exception not throwen on invalid height.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scalePercent( -23, -23 ); + $this->fail( 'Exception not throwen on invalid width and height.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testScalePercent_2() + { + $this->handler->scaleExact( 200, 200 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 300 + ); + } + + public function testScaleExact_1() + { + $this->handler->scaleExact( 200, 200 ); + $this->handler->scaleExact( 200, 200 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 300 + ); + } + + public function testScaleExact_2() + { + $this->handler->scaleExact( 10, 200 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 400 + ); + } + + public function testScaleExact_3() + { + $this->handler->scaleExact( 200, 10 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 450 + ); + } + + public function testScaleExactFailureInvalidParam() + { + try + { + $this->handler->scaleExact( -23, 100 ); + $this->fail( 'Exception not throwen on invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleExact( 100, -23 ); + $this->fail( 'Exception not throwen on invalid height.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->scaleExact( -23, -23 ); + $this->fail( 'Exception not throwen on invalid width and height.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testCrop_1() + { + $this->handler->crop( 50, 38, 50, 37 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCrop_2() + { + $this->handler->crop( 100, 75, -50, -37 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCrop_3() + { + $this->handler->crop( 50, 75, 250, 38 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCrop_0_Offset() + { + $this->handler->crop( 0, 0, 10, 10 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropNegativeOffset_1() + { + $this->handler->crop( -100, -100, 50, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropNegativeOffset_2() + { + $this->handler->crop( -50, -50, 50, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropNegativeOffset_3() + { + $this->handler->crop( -50, -50, -50, -50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testCropFailureInvalidParams() + { + try + { + $this->handler->crop( 'foo', 23, 23, 23 ); + $this->fail( 'Exception not thrown on crop with invalid x coordinate.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->crop( 23, 'foo', 23, 23 ); + $this->fail( 'Exception not thrown on crop with invalid y coordinate.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->crop( 23, 23, 'foo', 23 ); + $this->fail( 'Exception not thrown on crop with invalid width.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->crop( 23, 23, 23, 'foo' ); + $this->fail( 'Exception not thrown on crop with invalid height.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testColorspaceGrey() + { + $this->handler->colorspace( ezcImageColorspaceFilters::COLORSPACE_GREY ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 300 + ); + } + + public function testColorspaceMonochrome() + { + $this->handler->colorspace( ezcImageColorspaceFilters::COLORSPACE_MONOCHROME ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 10000 + ); + } + + public function testColorspaceSepia() + { + $this->handler->colorspace( ezcImageColorspaceFilters::COLORSPACE_SEPIA ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 5000 + ); + } + + public function testColorspaceFailureInvalidParam() + { + try + { + $this->handler->colorspace( 23 ); + $this->fail( 'Exception not thrown on invalid colorspace.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testNoiseUniform() + { + $this->handler->noise( 'Uniform' ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + // Noise is normally different each time + 200 + ); + } + + public function testNoiseGaussian() + { + $this->handler->noise( 'Gaussian' ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + // Noise is normally different each time + 30000 + ); + } + + public function testNoiseMultiplicative() + { + $this->handler->noise( 'Multiplicative' ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + // Noise is normally different each time + 30000 + ); + } + + public function testNoiseImpulse() + { + $this->handler->noise( 'Impulse' ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + // Noise is normally different each time + 10000 + ); + } + + public function testNoiseLaplacian() + { + $this->handler->noise( 'Laplacian' ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + // Noise is normally different each time + 22000 + ); + } + + public function testNoisePoisson() + { + $this->handler->noise( 'Poisson' ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + // Noise is normally different each time + 12000 + ); + } + + public function testNoiseFailureInvalidParam() + { + try + { + $this->handler->noise( 'foo' ); + $this->fail( 'Exception not thrown on invalid noise value.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testSwirl_10() + { + $this->handler->swirl( 10 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 2000 + ); + } + + public function testSwirl_50() + { + $this->handler->swirl( 50 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 2000 + ); + } + + public function testSwirl_100() + { + $this->handler->swirl( 100 ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 2000 + ); + } + + public function testSwirlFailureInvalidParam() + { + try + { + $this->handler->swirl( -23 ); + $this->fail( 'Exception not thrown on invalid swirl value.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testBorder_2() + { + $this->handler->border( 2, array( 0x00, 0x00, 0xFF ) ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testBorder_5() + { + $this->handler->border( 5, array( 255, 0, 0 ) ); + $this->handler->save( $this->getActiveReference(), $this->getTempPath() ); + $this->handler->close( $this->getActiveReference() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testBorderFailures() + { + try + { + $this->handler->border( false, array( 255, 0, 0 ) ); + $this->fail( "Border filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->border( 10, array( 255, false, 0 ) ); + $this->fail( "Border filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testWatermarkAbsoluteNoScale() + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", 100, 80 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testWatermarkAbsoluteScale() + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", 10, 10, 130, 93 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 100 + ); + } + + public function testWatermarkAbsoluteNoScaleNegativeOffset() + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -50, -33 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + strtr( $this->getReferencePath(), array( "NegativeOffset" => "" ) ), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testWatermarkAbsoluteScaleNegativeOffset() + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -140, -103, 130, 93 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + strtr( $this->getReferencePath(), array( "NegativeOffset" => "" ) ), + $this->getTempPath(), + "Image not rendered as expected.", + 100 + ); + } + + public function testWatermarkAbsoluteFailureInvalidParam() + { + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/foo.png", -140, -103, 130, 93 ); + $this->fail( 'Exception not throwen on invalid watermark file.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", 'foo', -103, 130, 93 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -140, 'foo', 130, 93 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -140, -103, 'foo', 93 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkAbsolute( dirname( __FILE__ ) . "/data/watermark.png", -140, -103, 130, 'foo' ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testWatermarkPercentNoScale() + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 10, 90 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testWatermarkPercentScale() + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 80, 80, 20 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 100 + ); + } + + public function testWatermarkPercentFailureInvalidParam() + { + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/foo.png", 80, 80, 20 ); + $this->fail( 'Exception not throwen on invalid watermark file.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", -80, 80, 20 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 80, -80, 20 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + + try + { + $this->handler->watermarkPercent( dirname( __FILE__ ) . "/data/watermark.png", 80, 80, -20 ); + $this->fail( 'Exception not throwen on invalid x coord.' ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testCropThumbnailVertical() + { + $this->handler->croppedThumbnail( 50, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 500 + ); + } + + public function testCropThumbnailHorizontal() + { + $this->handler->croppedThumbnail( 100, 50 ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 500 + ); + } + + public function testCroppedThumbnailFailures() + { + try + { + $this->handler->croppedThumbnail( -10, 50 ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->croppedThumbnail( "foo", 50 ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->croppedThumbnail( 50, -10 ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->croppedThumbnail( 50, false ); + $this->fail( "CroppedThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + } + + public function testFillThumbnailVertical() + { + $this->handler->filledThumbnail( 50, 50, array( 255, 0, 0 ) ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 500 + ); + } + + public function testFillThumbnailHorizontal() + { + $this->handler->filledThumbnail( 100, 50, array( 255, 0, 0 ) ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + 500 + ); + } + + public function testFillThumbnailTooLargeColorArray() + { + $this->handler->filledThumbnail( 100, 50, array( 255, 0, 0, 400, 500, 600 ) ); + $this->handler->save( $this->imageReference, $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not rendered as expected.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testFilledThumbnailFailures() + { + try + { + $this->handler->filledThumbnail( -10, 50, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( "foo", 50, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, -10, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, false, array( 255, 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, 50, array( 255, false, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + try + { + $this->handler->filledThumbnail( 50, 50, array( "bar", 0, 0 ) ); + $this->fail( "FilledThumbnail filter accepted incorrect value." ); + } + catch ( ezcBaseValueException $e ) + {} + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/handler_test.php b/include/ezcomponents/ImageConversion/tests/handler_test.php new file mode 100644 index 000000000..91efb21db --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/handler_test.php @@ -0,0 +1,214 @@ +handler = new $this->handlerClass( call_user_func( array( $this->handlerClass, "defaultSettings" ) ) ); + } + + protected function getReferences() + { + $handlerArr = ( array ) $this->handler; + $references = $handlerArr["\0ezcImageMethodcallHandler\0references"]; + return $references; + } + + public function testSaveOldfileNoconvert() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + copy( $srcPath, $dstPath ); + + $ref = $this->handler->load( $dstPath ); + + unlink( $dstPath ); + + $this->handler->save( $ref ); + + $this->assertTrue( + file_exists( $dstPath ), + "File not correctly saved to old destination." + ); + $this->handler->close( $ref ); + } + + public function testSaveNewfileNoconvert() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + $this->handler->save( $ref, $dstPath ); + + $this->assertTrue( + file_exists( $dstPath ), + "File not correctly saved to new destination." + ); + $this->handler->close( $ref ); + } + + public function testSaveNewfileConvert() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + $this->handler->save( $ref, $dstPath, "image/png" ); + + $analyzer = new ezcImageAnalyzer( $dstPath ); + + $this->assertEquals( + "image/png", + $analyzer->mime, + "File not correctly saved to new destination." + ); + $this->handler->close( $ref ); + } + + public function testSaveIllegalFileNameFailure() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath() . "$"; + + $ref = $this->handler->load( $srcPath ); + + $exceptionCaught = false; + try + { + $this->handler->save( $ref, $dstPath, "image/png" ); + } + catch ( ezcImageFileNameInvalidException $e ) + { + $this->handler->close( $ref ); + return; + } + $this->fail( "ezcImageFileNameInvalidException not thrown on illigal character $." ); + } + + public function testCloseFailure() + { + try + { + $this->handler->close( "abc" ); + } + catch ( ezcImageInvalidReferenceException $e ) + { + return; + } + $this->fail( "Exception not thrown on close of invalid reference." ); + } + + public function testAllowsInputSuccess() + { + $this->assertTrue( + $this->handler->allowsInput( "image/jpeg" ), + "Handler <{$this->handlerClass}> does not allow input of MIME type." + ); + } + + public function testAllowsInputFailure() + { + $this->assertFalse( + $this->handler->allowsInput( "foo/bar" ), + "Handler <{$this->handlerClass}> does allow input of weired MIME type." + ); + } + + public function testAllowsOutputSuccess() + { + $this->assertTrue( + $this->handler->allowsOutput( "image/jpeg" ), + "Handler <{$this->handlerClass}> does not allow input of MIME type." + ); + } + + public function testAllowsOutputFailure() + { + $this->assertFalse( + $this->handler->allowsOutput( "foo/bar" ), + "Handler <{$this->handlerClass}> does allow input of weired MIME type." + ); + } + + public function testHasFilterSuccess() + { + $this->assertTrue( + $this->handler->hasFilter( "scale" ), + "Does not every handler support the scale filter?" + ); + } + + public function testHasFilterFailure() + { + $this->assertFalse( + $this->handler->hasFilter( "ezc" ), + "Hey, who implements a filter called ??" + ); + } + + public function testGetFilterNamesSuccess() + { + $availFilters = $this->handler->getFilterNames(); + $geometryFilters = get_class_methods( "ezcImageGeometryFilters" ); + foreach ( $geometryFilters as $id => $filter ) + { + if ( substr( $filter, 0, 1 ) === "_" ) + { + unset( $geometryFilters[$id] ); + } + } + + $this->assertEquals( + array_intersect( $geometryFilters, $availFilters ), + $geometryFilters, + "Geometry filters seem not to be available in the filters for <{$this->handlerClass}>." + ); + } + + public function testGetFilterNamesFailure() + { + $availFilters = $this->handler->getFilterNames(); + $unavailFilters = array( "toby", "derick", "frederick", "ray", "__construct", "__destruct", "_whatever" ); + + $this->assertEquals( + array_intersect( $unavailFilters, $availFilters ), + array(), + "Weird filters seem not to be available in the filters for <{$this->handlerClass}>." + ); + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/handlergd_test.php b/include/ezcomponents/ImageConversion/tests/handlergd_test.php new file mode 100644 index 000000000..bf4b502a2 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/handlergd_test.php @@ -0,0 +1,374 @@ +markTestSkipped( $e->getMessage() ); + } + $this->handlerClass = "ezcImageGdHandler"; + parent::setUp(); + } + + public function testLoadSuccess() + { + $filePath = $this->testFiles["jpeg"]; + + $ref = $this->handler->load( $filePath ); + + $refProp = $this->getReferences(); + $imageRef = current( $refProp ); + + $this->assertEquals( + $imageRef["file"], + $filePath, + "Image reference not registered correctly." + ); + + $this->assertEquals( + $imageRef["mime"], + "image/jpeg", + "Image reference not registered correctly." + ); + } + + public function testLoadFailureFilenotexists() + { + $filePath = $this->testFiles["nonexistent"]; + + try + { + $ref = $this->handler->load( $filePath ); + } + catch ( ezcBaseFileNotFoundException $e ) + { + return; + } + $this->fail( "Required exception not thrown on not existing file." ); + } + + public function testLoadFailureUnknownmimetype() + { + $filePath = $this->testFiles["text"]; + + try + { + $ref = $this->handler->load( $filePath ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Required exception not thrown on not existing file." ); + } + + public function testLoadFailureUnknownMimeTypeParam() + { + $filePath = $this->testFiles['png']; + + try + { + $ref = $this->handler->load( $filePath, 'text/plain' ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Required exception not thrown on not existing file." ); + } + + public function testSaveOldfileNoconvert() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + copy( $srcPath, $dstPath ); + + $copytime = filemtime( $dstPath ); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $dstPath ); + + unlink( $dstPath ); + + $handler->save( $ref ); + + $this->assertTrue( + file_exists( $dstPath ), + "File not correctly saved to old destination." + ); + } + + public function testSaveNewfileNoconvert() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + $handler->save( $ref, $dstPath ); + + $this->assertTrue( + file_exists( $dstPath ), + "File not correctly saved to new destination." + ); + } + + public function testSaveNewfileConvert() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + $handler->save( $ref, $dstPath, "image/png" ); + + $analyzer = new ezcImageAnalyzer( $dstPath ); + + $this->assertEquals( + "image/png", + $analyzer->mime, + "File not correctly saved to new destination." + ); + } + + public function testSaveNewfileQualityLow() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->quality = 0; + + $handler->save( $ref, $dstPath, "image/jpeg", $opts ); + + $this->assertTrue( + filesize( $dstPath ) < 2000, + "File saved with too high quality." + ); + } + + public function testSaveNewfileQualityHigh() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->quality = 100; + + $handler->save( $ref, $dstPath, "image/jpeg", $opts ); + + $this->assertTrue( + filesize( $dstPath ) > 30000, + "File saved with too low quality." + ); + } + + public function testSaveNewfileCompressionLow() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->compression = 0; + + $handler->save( $ref, $dstPath, "image/png", $opts ); + + $this->assertTrue( + filesize( $dstPath ) > 100000, + "File saved with too high compression." + ); + } + + public function testSaveNewfileCompressionHigh() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->compression = 9; + + $handler->save( $ref, $dstPath, "image/png", $opts ); + + $this->assertTrue( + filesize( $dstPath ) < 40000, + "File saved with too low compression." + ); + } + + public function testSaveFailureUnknownMimeType() + { + $srcPath = $this->testFiles['jpeg']; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + try + { + $handler->save( $ref, $dstPath, 'text/plain' ); + $this->fail( 'Exception not thrown on save with invalid MIME type.' ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + {} + + $handler->close( $ref ); + } + + public function testConvertSuccess() + { + $filePath = $this->testFiles["jpeg"]; + + $ref = $this->handler->load( $filePath ); + $this->handler->convert( $ref, "image/png" ); + + $refProp = $this->getReferences(); + $imageRef = current( $refProp ); + + $this->assertTrue( + $imageRef["mime"] === "image/png", + "MIME type conversion not registered correctly." + ); + } + + public function testConvertFailure() + { + $filePath = $this->testFiles["jpeg"]; + + $ref = $this->handler->load( $filePath ); + + try + { + $this->handler->convert( $ref, "application/ezc" ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Exception for unknown conversion not thrown." ); + } + + public function testApplyFilterSingle() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + $this->handler->applyFilter( $ref, new ezcImageFilter( "scale", array( "width" => 200, "height" => 200, "direction" => ezcImageGeometryFilters::SCALE_BOTH ) ) ); + $this->handler->save( $ref, $dstPath ); + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Applying single filter through handler failed.", + // ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + 60 + ); + } + + public function testApplyFilterMultiple() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + $this->handler->applyFilter( $ref, new ezcImageFilter( "scale", array( "width" => 200, "height" => 200, "direction" => ezcImageGeometryFilters::SCALE_BOTH ) ) ); + $this->handler->applyFilter( $ref, new ezcImageFilter( "crop", array( "x" => 50, "width" => 100, "y" => 50, "height" => 100 ) ) ); + $this->handler->applyFilter( $ref, new ezcImageFilter( "colorspace", array( "space" => ezcImageColorspaceFilters::COLORSPACE_SEPIA ) ) ); + $this->handler->save( $ref, $dstPath ); + + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Applying multiple filter through handler failed.", + // ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + 80 + ); + } + + public function testApplyFilterFailureNonExistent() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + + try + { + $this->handler->applyFilter( + $ref, + new ezcImageFilter( + "non-existent", + array( "width" => 200, "height" => 200, "direction" => ezcImageGeometryFilters::SCALE_BOTH ) + ) + ); + $this->fail( 'Exception not throwen on apply of non-existent filter.' ); + } + catch ( ezcImageFilterNotAvailableException $e ) + {} + } + + public function testConvertTransparentNonTransparent() + { + + $srcPath = $this->testFiles["png_transparent"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + + $options = new ezcImageSaveOptions(); + $options->transparencyReplacementColor = array( 255, 0, 0 ); + + $this->handler->save( $ref, $dstPath, 'image/jpeg', $options ); + + $this->handler->close( $ref ); + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Converting transparent background failed.", + 500 + ); + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/handlershell_test.php b/include/ezcomponents/ImageConversion/tests/handlershell_test.php new file mode 100644 index 000000000..933d2a928 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/handlershell_test.php @@ -0,0 +1,281 @@ +markTestSkipped( $e->getMessage() ); + } + $this->handlerClass = "ezcImageImagemagickHandler"; + parent::setUp(); + } + + + public function testLoadSuccess() + { + $filePath = $this->testFiles["jpeg"]; + + $ref = $this->handler->load( $filePath ); + + $refProp = $this->getReferences(); + $imageRef = current( $refProp ); + + $this->handler->close( $ref ); + $this->assertSame( + $filePath, + $imageRef["file"], + "Image reference not registered correctly." + ); + + $this->assertSame( + $imageRef["mime"], + "image/jpeg", + "Image reference not registered correctly." + ); + + } + + public function testLoadFailureFilenotexists() + { + $filePath = $this->testFiles["nonexistent"]; + + try + { + $ref = $this->handler->load( $filePath ); + } + catch ( ezcBaseFileNotFoundException $e ) + { + return; + } + $this->fail( "Required exception not thrown on not existing file." ); + } + + public function testLoadFailureUnknownmimetype() + { + $filePath = $this->testFiles["text"]; + + try + { + $ref = $this->handler->load( $filePath ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Required exception not thrown on not existing file." ); + } + + public function testCloseSuccess() + { + $srcPath = $this->testFiles["jpeg"]; + $ref = $this->handler->load( $srcPath ); + + $refProp = $this->getReferences(); + $tmpFile = $refProp[$ref]["resource"]; + + $this->handler->close( $ref ); + + $refProp = $this->getReferences(); + + $this->assertFalse( + isset( $refProp[$ref] ), + "Reference not freed successfully." + ); + $this->assertFalse( + file_exists( $tmpFile ), + "Temporary file not deleted successfully." + ); + } + + public function testRemoveTempFilesInDtorSuccess() + { + $filePath = $this->testFiles["jpeg"]; + + $ref = $this->handler->load( $filePath ); + + $refProp = $this->getReferences(); + $imageRef = current( $refProp ); + + // Manually destruct handler + unset( $this->handler ); + + $this->assertFalse( + file_exists( $imageRef["resource"] ), + "Image reference not closed correctly in dtor." + ); + + } + + public function testApplyFilterSingle() + { + + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + $this->handler->applyFilter( $ref, new ezcImageFilter( "scale", array( "width" => 200, "height" => 200, "direction" => ezcImageGeometryFilters::SCALE_BOTH ) ) ); + $this->handler->save( $ref, $dstPath ); + $this->handler->close( $ref ); + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Applying single filter through handler failed.", + 300 + ); + } + + public function testApplyFilterMultiple() + { + $srcPath = $this->testFiles["jpeg"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + + $this->handler->applyFilter( $ref, new ezcImageFilter( "scale", array( "width" => 200, "height" => 200, "direction" => ezcImageGeometryFilters::SCALE_BOTH ) ) ); + $this->handler->applyFilter( $ref, new ezcImageFilter( "crop", array( "x" => 50, "width" => 100, "y" => 50, "height" => 100 ) ) ); + $this->handler->applyFilter( $ref, new ezcImageFilter( "colorspace", array( "space" => ezcImageColorspaceFilters::COLORSPACE_SEPIA ) ) ); + + $this->handler->save( $ref, $dstPath ); + + $this->handler->close( $ref ); + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Applying multiple filter through handler failed.", + // ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + 12000 + ); + // @ todo: Orphan! Remove! + $this->removeTempDir(); + } + + public function testSaveNewfileQualityLow() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->quality = 0; + + $handler->save( $ref, $dstPath, "image/jpeg", $opts ); + + $this->assertTrue( + filesize( $dstPath ) < 2000, + "File saved with too high quality." + ); + } + + public function testSaveNewfileQualityHigh() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->quality = 100; + + $handler->save( $ref, $dstPath, "image/jpeg", $opts ); + + $this->assertTrue( + filesize( $dstPath ) > 30000, + "File saved with too low quality." + ); + } + + public function testSaveNewfileCompressionLow() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->compression = 0; + + $handler->save( $ref, $dstPath, "image/png", $opts ); + + $this->assertTrue( + filesize( $dstPath ) > 100000, + "File saved with too high compression." + ); + } + + public function testSaveNewfileCompressionHigh() + { + $srcPath = $this->testFiles["png"]; + $dstPath = $this->getTempPath(); + + $handler = new ezcImageGdHandler( ezcImageGdHandler::defaultSettings() ); + $ref = $handler->load( $srcPath ); + + $opts = new ezcImageSaveOptions(); + $opts->compression = 9; + + $handler->save( $ref, $dstPath, "image/png", $opts ); + + $this->assertTrue( + filesize( $dstPath ) < 40000, + "File saved with too low compression." + ); + } + + public function testConvertTransparentNonTransparent() + { + + $srcPath = $this->testFiles["png_transparent"]; + $dstPath = $this->getTempPath(); + + $ref = $this->handler->load( $srcPath ); + + $options = new ezcImageSaveOptions(); + $options->transparencyReplacementColor = array( 255, 0, 0 ); + + $this->handler->save( $ref, $dstPath, 'image/jpeg', $options ); + + $this->handler->close( $ref ); + $this->assertImageSimilar( + $this->getReferencePath(), + $dstPath, + "Converting transparent background failed.", + 500 + ); + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/save_options_test.php b/include/ezcomponents/ImageConversion/tests/save_options_test.php new file mode 100644 index 000000000..8d863ba51 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/save_options_test.php @@ -0,0 +1,103 @@ +assertNull( $opt->compression ); + $this->assertNull( $opt->quality ); + $this->assertNull( $opt->transparencyReplacementColor ); + } + + public function testGetAccessFailure() + { + $opt = new ezcImageSaveOptions(); + + try + { + echo $opt->foo; + } + catch ( ezcBasePropertyNotFoundException $e ) + { + return; + } + $this->fail( "ezcBasePropertyNotFoundException not thrown on get access to invalid property foo." ); + } + + public function testSetAccessSuccess() + { + $opt = new ezcImageSaveOptions(); + + $this->assertSetProperty( + $opt, + 'compression', + range( 0, 9, 1 ) + ); + $this->assertSetProperty( + $opt, + 'quality', + range( 0, 100, 10 ) + ); + $this->assertSetProperty( + $opt, + 'transparencyReplacementColor', + array( + array( 23, 42, 13 ), + array( 0, 0, 0 ), + ) + ); + } + + public function testSetAccessFailure() + { + $opt = new ezcImageSaveOptions(); + + $this->assertSetPropertyFails( + $opt, + 'compression', + array( true, false, 23.42, 'foo', array(), new stdClass(), -1, 10, -23 ) + ); + $this->assertSetPropertyFails( + $opt, + 'quality', + array( true, false, 23.42, 'foo', array(), new stdClass(), -1, 101, -23 ) + ); + $this->assertSetPropertyFails( + $opt, + 'transparencyReplacementColor', + array( + true, false, 23.42, 'foo', array(), new stdClass(), -1, 101, + array( 42, 23 ), array( 'foo' => 42, 'bar' => 23 ), + array( 1 => 0, 2 => 0, 3 => 0 ), array( 'foo' => 'bar' ) + ) + ); + + try + { + $opt->foo = 23; + } + catch ( ezcBasePropertyNotFoundException $e ) + { + return; + } + $this->fail( "ezcBasePropertyNotFoundException not thrown on set access to invalid property foo." ); + } +} + +?> diff --git a/include/ezcomponents/ImageConversion/tests/suite.php b/include/ezcomponents/ImageConversion/tests/suite.php new file mode 100644 index 000000000..e5b03237c --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/suite.php @@ -0,0 +1,44 @@ +setName( "ImageConversion" ); + + $this->addTest( ezcImageConversionConverterTest::suite() ); + $this->addTest( ezcImageConversionTransformationTest::suite() ); + + $this->addTest( ezcImageConversionHandlerGdTest::suite() ); + $this->addTest( ezcImageConversionFiltersGdTest::suite() ); + + $this->addTest( ezcImageConversionHandlerShellTest::suite() ); + $this->addTest( ezcImageConversionFiltersShellTest::suite() ); + + $this->addTest( ezcImageConversionSaveOptionsTest::suite() ); + } + + public static function suite() + { + return new ezcImageConversionSuite( "ezcImageConversionSuite" ); + } +} +?> diff --git a/include/ezcomponents/ImageConversion/tests/test_case.php b/include/ezcomponents/ImageConversion/tests/test_case.php new file mode 100644 index 000000000..7badc94cd --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/test_case.php @@ -0,0 +1,100 @@ +testFiles[basename( $pathInfo["basename"], "." . $pathInfo["extension"] )] = realpath( $testFile ); + } + $this->testFiles["nonexistent"] = "nonexistent.jpg"; + $this->referencePath = "$dataDir/compare"; + } + + public function __destruct() + { + if ( ezcImageConversionTestCase::REMOVE_TEMP_DIRS === true ) + { + $this->removeTempDir(); + unset( ezcImageConversionTestCase::$tempDirs[get_class( $this )] ); + } + } + + protected function setUp() + { + if ( !ezcBaseFeatures::hasExtensionSupport( 'gd' ) ) + { + $this->markTestSkipped( 'ext/gd is required to run this test.' ); + } + } + + protected function getTempPath( $index = "" ) + { + return ezcImageConversionTestCase::REGENERATION_MODE === true + ? "{$this->referencePath}/{$this->getTestName( $index )}" + : "{$this->getTempBasePath()}/{$this->getTestName( $index )}"; + } + + protected function getReferencePath( $index = "" ) + { + return "{$this->referencePath}/{$this->getTestName( $index )}"; + } + + private function getTestName ( $index ) + { + $trace = debug_backtrace(); + if ( !isset( $trace[2]["class"] ) || !isset( $trace[2]["function"] ) ) + { + $this->fail( "BROKEN TEST CASE. MISSING OBJECT OR FUNCTION IN BACKTRACE" ); + } + return $trace[2]["class"] . "_" . $trace[2]["function"] . $index; + } + + private function getTempBasePath() + { + if ( !isset( ezcImageConversionTestCase::$tempDirs[get_class( $this )] ) ) + { + ezcImageConversionTestCase::$tempDirs[get_class( $this )] = $this->createTempDir( get_class( $this ) ); + } + return ezcImageConversionTestCase::$tempDirs[get_class( $this )]; + } +} + +?> diff --git a/include/ezcomponents/ImageConversion/tests/transformation_test.php b/include/ezcomponents/ImageConversion/tests/transformation_test.php new file mode 100644 index 000000000..90e1f1d19 --- /dev/null +++ b/include/ezcomponents/ImageConversion/tests/transformation_test.php @@ -0,0 +1,970 @@ +testFiltersSuccess = array( + 0 => array( + 0 => new ezcImageFilter( + "scaleExact", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + 1 => new ezcImageFilter( + "crop", + array( + "x" => 10, + "width" => 30, + "y" => 10, + "height"=> 30, + ) + ), + 2 => new ezcImageFilter( + "colorspace", + array( + "space" => ezcImageColorspaceFilters::COLORSPACE_GREY, + ) + ), + ), + 1 => array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 1000, + "direction" => ezcImageGeometryFilters::SCALE_DOWN, + ) + ), + 2 => new ezcImageFilter( + "colorspace", + array( + "space" => ezcImageColorspaceFilters::COLORSPACE_MONOCHROME, + ) + ), + ), + 2 => array( + 0 => new ezcImageFilter( + "scaleHeight", + array( + "height" => 70, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + 2 => new ezcImageFilter( + "colorspace", + array( + "space" => ezcImageColorspaceFilters::COLORSPACE_SEPIA, + ) + ), + ), + // Optional parameter dismissed + 3 => array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + ) + ), + ), + ); + $this->testFiltersFailure = array( + // Nonexistant filter + 0 => array( + 0 => new ezcImageFilter( + "toby", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + 1 => new ezcImageFilter( + "crop", + array( + "x" => 10, + "width" => 30, + "y" => 10, + "height"=> 30, + ) + ), + 2 => new ezcImageFilter( + "colorspace", + array( + "space" => ezcImageColorspaceFilters::COLORSPACE_GREY, + ) + ), + ), + // Missing option + 1 => array( + 0 => new ezcImageFilter( + "scale", + array( + ) + ), + 2 => new ezcImageFilter( + "colorspace", + array( + "space" => ezcImageColorspaceFilters::COLORSPACE_MONOCHROME, + ) + ), + ), + ); + + $conversionsIn = array( + "image/gif" => "image/png", + "image/xpm" => "image/jpeg", + "image/wbmp" => "image/jpeg", + ); + if ( ezcBaseFeatures::os() === 'Windows' ) + { + unset( $conversionsIn["image/xpm"] ); + } + + $settings = new ezcImageConverterSettings( + array( new ezcImageHandlerSettings( "GD", "ezcImageGdHandler" ) ), + $conversionsIn + ); + $this->converter = new ezcImageConverter( $settings ); + } + catch ( Exception $e ) + { + $this->markTestSkipped( $e->getMessage() ); + } + } + + protected function tearDown() + { + unset( $this->converter ); + } + + public function testConstructSuccess() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + 1 => new ezcImageFilter( + "scaleWidth", + array( + "width" => 40, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + 2 => new ezcImageFilter( + "crop", + array( + "xStart" => 10, + "xEnd" => 40, + "yStart" => 10, + "yEnd" => 40, + ) + ), + ); + + $mimeIn = array( "image/jpeg" ); + + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + + $this->assertAttributeEquals( + $mimeIn, + "mimeOut", + $trans, + "MIME types not registered correctly in transformation." + ); + $this->assertAttributeEquals( + $filtersIn, + "filters", + $trans, + "Filters not registered correctly in transformation." + ); + } + + public function testConstructFailureFilterNotAvailable() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "toby", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + ); + + $mimeIn = array( "image/jpeg" ); + + try + { + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + } + catch ( ezcImageFilterNotAvailableException $e ) + { + return; + } + $this->fail( "Transformation did not throw exception on invalid filter." ); + } + + public function testConstructFailureInvalidMimeType() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + ); + + $mimeIn = array( "application/toby" ); + + try + { + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + } + catch ( ezcImageMimeTypeUnsupportedException $e ) + { + return; + } + $this->fail( "Transformation did not throw exception on invalid MIME type." ); + } + + public function testAddFilterSuccess() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + ); + + $newFilter = new ezcImageFilter( + "scaleWidth", + array( + "width" => 40, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ); + + $filtersOut = $filtersIn; + $filtersOut[] = $newFilter; + + $mimeIn = array( "image/jpeg" ); + + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + + $trans->addFilter( $newFilter ); + + $this->assertAttributeEquals( + $filtersOut, + "filters", + $trans, + "Filters not added correctly to transformation." + ); + } + + public function testAddFilterFailure() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + ); + + $newFilter = new ezcImageFilter( + "toby", + array( + "width" => 40, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ); + + $filtersOut = $filtersIn; + $filtersOut[] = $newFilter; + + $mimeIn = array( "image/jpeg" ); + + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + + try + { + $trans->addFilter( $newFilter ); + } + catch ( ezcImageFilterNotAvailableException $e ) + { + return; + } + $this->fail( "Transformation did not throw exception on invalid filter." ); + } + + public function testGetOutMimeSuccessNoTransform() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + ); + + $mimeIn = array( "image/jpeg" ); + + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + + $this->assertEquals( + "image/jpeg", + $trans->getOutMime( $this->testFiles["jpeg"] ), + "Transformation returned incorrect output MIME type." + ); + } + + public function testGetOutMimeSuccessExplicitTransform() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + ); + + $mimeIn = array( "image/jpeg", "image/png" ); + + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + + $this->assertEquals( + "image/png", + $trans->getOutMime( $this->testFiles["gif_nonanimated"] ), + "Transformation returned incorrect output MIME type." + ); + } + + public function testGetOutMimeSuccessImplicitTransform() + { + $filtersIn = array( + 0 => new ezcImageFilter( + "scale", + array( + "width" => 50, + "height" => 50, + "direction" => ezcImageGeometryFilters::SCALE_BOTH, + ) + ), + ); + + $mimeIn = array( "image/jpeg" ); + + $trans = new ezcImageTransformation( $this->converter, "test", $filtersIn, $mimeIn ); + + $this->assertEquals( + "image/jpeg", + $trans->getOutMime( $this->testFiles["gif_nonanimated"] ), + "Transformation returned incorrect output MIME type." + ); + } + + public function testTransformSuccessPng_1() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[0], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["png"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + // ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + 20 + ); + } + + public function testTransformFailureText() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[0], + array( "image/jpeg", "image/png" ) + ); + + try + { + $trans->transform( $this->testFiles["text"], $this->getTempPath() ); + } + catch ( ezcImageTransformationException $e ) + { + return; + } + $this->fail( "Exception not thrown on invalid image input." ); + } + + public function testTransformSuccessPng_2() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[1], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["png"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testTransformSuccessPng_3() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[2], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["png"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + // ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + 40 + ); + } + + public function testTransformSuccessPng_4() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[3], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["png"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + // ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + 20 + ); + } + + public function testTransformSuccessJpeg_1() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[0], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["jpeg"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testTransformSuccessJpeg_2() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[1], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["jpeg"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testTransformSuccessJpeg_3() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[2], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["jpeg"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testTransformSuccessGif_1() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[0], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["gif_nonanimated"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testTransformSuccessGif_2() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[1], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["gif_nonanimated"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testTransformSuccessGif_3() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[2], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["gif_nonanimated"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + ezcImageConversionTestCase::DEFAULT_SIMILARITY_GAP + ); + } + + public function testTransformSuccessGifAnimated() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersSuccess[2], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["gif_animated"], $this->getTempPath() ); + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully.", + 7000 + ); + } + + public function testTransformFailureFilterNotAvailable() + { + try + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersFailure[0], + array( "image/jpeg", "image/png" ) + ); + $trans->transform( $this->testFiles["jpeg"], $this->getTempPath() ); + } + catch ( ezcImageFilterNotAvailableException $e ) + { + return; + } + $this->fail( "Expected exception not thrown." ); + + } + + public function testTransformFailureMissingFilterOption() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersFailure[1], + array( "image/jpeg", "image/png" ) + ); + try + { + $trans->transform( $this->testFiles["jpeg"], $this->getTempPath() ); + } + catch ( ezcImageTransformationException $e ) + { + return; + } + $this->fail( "Expected exception not thrown." ); + + } + + public function testTransformFailureFileNotFound() + { + $trans = new ezcImageTransformation( + $this->converter, + "test", + $this->testFiltersFailure[1], + array( "image/jpeg", "image/png" ) + ); + try + { + $trans->transform( $this->testFiles["nonexistent"], $this->getTempPath() ); + } + catch ( ezcBaseFileNotFoundException $e ) + { + return; + } + $this->fail( "Expected exception not thrown." ); + + } + + // Test for bug #8137: ImageConversion - ezcImageTransformation fails on + public function testMultiTransform() + { + $mimeOut = array( "image/jpeg" ); + $trans = new ezcImageTransformation( $this->converter, "test", $this->testFiltersSuccess[0], $mimeOut ); + + $trans->transform( $this->testFiles["jpeg"], $this->getTempPath( "jpeg" ) ); + $trans->transform( $this->testFiles["png"], $this->getTempPath( "png" ) ); + + $this->assertImageSimilar( + $this->getReferencePath( "jpeg" ), + $this->getTempPath( "jpeg" ), + "Transformation did not produce correct output.", + 2000 + ); + $this->assertImageSimilar( + $this->getReferencePath( "png" ), + $this->getTempPath( "png" ), + "Transformation did not produce correct output.", + 2000 + ); + } + + // Test for bug #10949: rename php error if file allread exists + public function testDoubleTransform() + { + $mimeOut = array( "image/jpeg" ); + $trans = new ezcImageTransformation( $this->converter, "test", $this->testFiltersSuccess[0], $mimeOut ); + + $resFile = $this->getTempPath( "jpeg" ); + $trans->transform( $this->testFiles["jpeg"], $resFile ); + $trans->transform( $this->testFiles["jpeg"], $resFile ); + + // Should not fail or produce a notice + } + + public function testTransformQualityLow() + { + $mimeOut = array( "image/jpeg" ); + $opts = new ezcImageSaveOptions(); + $opts->quality = 0; + // irrelevant, but set! + $opts->compression = 9; + $dstPath = $this->getTempPath( "jpeg" ); + + $trans = new ezcImageTransformation( $this->converter, "test", array(), $mimeOut, $opts ); + $trans->transform( $this->testFiles["png"], $dstPath ); + + $this->assertThat( + filesize( $dstPath ), + $this->lessThan( 2000 ), + "File saved with too high quality." + ); + } + + public function testTransformQualityHigh() + { + $mimeOut = array( "image/jpeg" ); + $opts = new ezcImageSaveOptions(); + $opts->quality = 100; + // irrelevant, but set! + $opts->compression = 9; + $dstPath = $this->getTempPath( "jpeg" ); + + $trans = new ezcImageTransformation( $this->converter, "test", array(), $mimeOut, $opts ); + $trans->transform( $this->testFiles["png"], $dstPath ); + + $this->assertThat( + filesize( $dstPath ), + $this->greaterThan( 30000 ), + "File saved with too low quality." + ); + } + + public function testTransformCompressionLow() + { + $mimeOut = array( "image/png" ); + $opts = new ezcImageSaveOptions(); + $opts->compression = 0; + // irrelevant, but set! + $opts->quality = 100; + $dstPath = $this->getTempPath( "png" ); + + $trans = new ezcImageTransformation( $this->converter, "test", array(), $mimeOut, $opts ); + $trans->transform( $this->testFiles["png"], $dstPath ); + + $this->assertThat( + filesize( $dstPath ), + $this->greaterThan( 100000 ), + "File saved with too high compression." + ); + } + + public function testTransformCompressionHigh() + { + $mimeOut = array( "image/png" ); + $opts = new ezcImageSaveOptions(); + $opts->compression = 9; + // irrelevant, but set! + $opts->quality = 100; + $dstPath = $this->getTempPath( "png" ); + + $trans = new ezcImageTransformation( $this->converter, "test", array(), $mimeOut, $opts ); + $trans->transform( $this->testFiles["png"], $dstPath ); + + $this->assertThat( + filesize( $dstPath ), + $this->lessThan( 40000 ), + "File saved with too low compression." + ); + } + + public function testApplyTransformationFailureFileNotReadable() + { + $tmpDir = $this->createTempDir( __CLASS__ ); + $srcFile = "$tmpDir/non_readable_png.png"; + + copy( $this->testFiles['png'], $srcFile ); + chmod( $srcFile, 0000 ); + + $trans = new ezcImageTransformation( $this->converter, "test", array(), array( 'image/jpeg' ) ); + try + { + $trans->transform( $srcFile, $srcFile ); + $this->fail( 'Exception not throwen with unreadable file.' ); + } + catch ( ezcBaseFilePermissionException $e ) + {} + + $this->removeTempDir(); + } + + public function testApplyTransformationFailureDestinationNotOverwriteable() + { + $tmpDir = $this->createTempDir( __CLASS__ ); + $dstFile = "$tmpDir/non_writeable_png.png"; + + touch( $dstFile ); + chmod( dirname( $dstFile ), 0555 ); + clearstatcache(); + + $trans = new ezcImageTransformation( $this->converter, "test", array(), array( 'image/jpeg' ) ); + try + { + $trans->transform( $this->testFiles['png'], $dstFile ); + $this->fail( 'Exception not throwen with not writeable file.' ); + } + catch ( ezcImageFileNotProcessableException $e ) + {} + + chmod( dirname( $dstFile ), 0777 ); + clearstatcache(); + + $this->removeTempDir(); + } + + public function testCreateTransformationFailureInvalidFilters() + { + $filters = $this->testFiltersSuccess[0]; + $filters[] = new stdClass(); + + try + { + $trans = new ezcImageTransformation( $this->converter, 'test', $filters, array( 'image/jpeg' ) ); + $this->fail( 'Exception not throwen on invalid filter in initial filter array.' ); + } + catch ( ezcBaseSettingValueException $e ) + {} + } + + public function testAddFilterBefore() + { + $newFilter = new ezcImageFilter( + 'scale', + array( 'width' => 10, 'height' => 10 ) + ); + $filtersBefore = $this->testFiltersSuccess[0]; + $filtersAfter = $filtersBefore; + array_splice( $filtersAfter, 1, 0, array( $newFilter ) ); + + $trans = new ezcImageTransformation( $this->converter, 'test', $filtersBefore, array( 'image/jpeg' ) ); + + $trans->addFilter( $newFilter, 1 ); + + $this->assertAttributeEquals( + $filtersAfter, + 'filters', + $trans + ); + } + + public function testTransformationChangingHandlersForFilters() + { + $gdSettings = new ezcImageHandlerSettings( 'GD', 'ezcImageGdHandler' ); + $imSettings = new ezcImageHandlerSettings( 'IM', 'ezcImageImagemagickHandler'); + try + { + $gd = new ezcImageGdHandler( $gdSettings ); + $im = new ezcImageImagemagickHandler( $imSettings ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + $this->markTestSkipped( 'Needs both image handlers.' ); + } + + $conv = new ezcImageConverter( + new ezcImageConverterSettings( + array( $gdSettings, $imSettings ) + ) + ); + + $trans = new ezcImageTransformation( + $conv, + 'test', + array( + new ezcImageFilter( + 'scale', + array( 'width' => 100, 'height' => 100 ) + ), + new ezcImageFilter( + 'swirl', + array( 'value' => 100 ) + ), + ), + array( 'image/png' ) + ); + + + $trans->transform( $this->testFiles['png'], $this->getTempPath() ); + + $this->assertImageSimilar( + $this->getReferencePath(), + $this->getTempPath(), + "Image not generated successfully", + 500 + ); + } + + public function testTransformationChangingHandlersForConversion() + { + $gdSettings = new ezcImageHandlerSettings( 'GD', 'ezcImageGdHandler' ); + $imSettings = new ezcImageHandlerSettings( 'IM', 'ezcImageImagemagickHandler'); + try + { + $gd = new ezcImageGdHandler( $gdSettings ); + $im = new ezcImageImagemagickHandler( $imSettings ); + } + catch ( ezcImageHandlerNotAvailableException $e ) + { + $this->markTestSkipped( 'Needs both image handlers.' ); + } + + $conv = new ezcImageConverter( + new ezcImageConverterSettings( + array( $gdSettings, $imSettings ) + ) + ); + + $trans = new ezcImageTransformation( + $conv, + 'test', + array( + new ezcImageFilter( + 'scale', + array( 'width' => 100, 'height' => 100 ) + ), + ), + array( 'image/g3fax' ) + ); + + + $trans->transform( $this->testFiles['png'], $this->getTempPath() ); + + // No assertion, must simply not throw an exception and just raises code coverage + } + +} +?> diff --git a/include/ezcomponents/LICENSE b/include/ezcomponents/LICENSE new file mode 100644 index 000000000..7c28222ef --- /dev/null +++ b/include/ezcomponents/LICENSE @@ -0,0 +1,32 @@ +eZ Components Licence +===================== + +New BSD Licence +--------------- + +Copyright (c) 2005-2008, eZ Systems A.S. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. +* Neither the name of eZ Systems A.S. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/include/ezcomponents/autoload/archive_autoload.php b/include/ezcomponents/autoload/archive_autoload.php new file mode 100644 index 000000000..bfb1d9a57 --- /dev/null +++ b/include/ezcomponents/autoload/archive_autoload.php @@ -0,0 +1,45 @@ + 'Archive/exceptions/archive_exception.php', + 'ezcArchiveBlockSizeException' => 'Archive/exceptions/archive_block_size.php', + 'ezcArchiveChecksumException' => 'Archive/exceptions/archive_checksum.php', + 'ezcArchiveEmptyException' => 'Archive/exceptions/archive_empty.php', + 'ezcArchiveEntryPrefixException' => 'Archive/exceptions/archive_entry_prefix.php', + 'ezcArchiveInternalException' => 'Archive/exceptions/archive_internal_exception.php', + 'ezcArchiveIoException' => 'Archive/exceptions/archive_io.php', + 'ezcArchiveUnknownTypeException' => 'Archive/exceptions/archive_unknown_type.php', + 'ezcArchiveValueException' => 'Archive/exceptions/archive_value.php', + 'ezcArchive' => 'Archive/archive.php', + 'ezcArchiveV7Header' => 'Archive/tar/headers/tar_v7.php', + 'ezcArchiveV7Tar' => 'Archive/tar/v7_tar.php', + 'ezcArchiveFile' => 'Archive/file/file.php', + 'ezcArchiveLocalFileHeader' => 'Archive/zip/headers/zip_local_file.php', + 'ezcArchiveUstarHeader' => 'Archive/tar/headers/tar_ustar.php', + 'ezcArchiveUstarTar' => 'Archive/tar/ustar_tar.php', + 'ezcArchiveBlockFile' => 'Archive/file/block_file.php', + 'ezcArchiveCentralDirectoryEndHeader' => 'Archive/zip/headers/zip_central_directory_end.php', + 'ezcArchiveCentralDirectoryHeader' => 'Archive/zip/headers/zip_central_directory.php', + 'ezcArchiveCharacterFile' => 'Archive/file/character_file.php', + 'ezcArchiveChecksums' => 'Archive/checksums.php', + 'ezcArchiveEntry' => 'Archive/archive_entry.php', + 'ezcArchiveFileStructure' => 'Archive/file_structure.php', + 'ezcArchiveGnuHeader' => 'Archive/tar/headers/tar_gnu.php', + 'ezcArchiveGnuTar' => 'Archive/tar/gnu_tar.php', + 'ezcArchiveMime' => 'Archive/archive_mime.php', + 'ezcArchiveOptions' => 'Archive/options/archive.php', + 'ezcArchivePaxHeader' => 'Archive/tar/headers/tar_pax.php', + 'ezcArchivePaxTar' => 'Archive/tar/pax_tar.php', + 'ezcArchiveStatMode' => 'Archive/stat_mode.php', + 'ezcArchiveZip' => 'Archive/zip/zip.php', +); +?> diff --git a/include/ezcomponents/autoload/authentication_autoload.php b/include/ezcomponents/autoload/authentication_autoload.php new file mode 100644 index 000000000..11dbba85c --- /dev/null +++ b/include/ezcomponents/autoload/authentication_autoload.php @@ -0,0 +1,52 @@ + 'Authentication/exceptions/authentication_exception.php', + 'ezcAuthenticationLdapException' => 'Authentication/exceptions/ldap_exception.php', + 'ezcAuthenticationOpenidException' => 'Authentication/exceptions/openid_exception.php', + 'ezcAuthenticationTypekeyException' => 'Authentication/exceptions/typekey_exception.php', + 'ezcAuthenticationBignumLibrary' => 'Authentication/math/bignum_library.php', + 'ezcAuthenticationCredentials' => 'Authentication/credentials/credentials.php', + 'ezcAuthenticationDataFetch' => 'Authentication/interfaces/data_fetch.php', + 'ezcAuthenticationFilter' => 'Authentication/interfaces/authentication_filter.php', + 'ezcAuthenticationFilterOptions' => 'Authentication/options/filter_options.php', + 'ezcAuthenticationOpenidStore' => 'Authentication/filters/openid/openid_store.php', + 'ezcAuthenticationOpenidStoreOptions' => 'Authentication/options/openid_store_options.php', + 'ezcAuthentication' => 'Authentication/authentication.php', + 'ezcAuthenticationBcmathLibrary' => 'Authentication/math/bcmath_library.php', + 'ezcAuthenticationGmpLibrary' => 'Authentication/math/gmp_library.php', + 'ezcAuthenticationGroupFilter' => 'Authentication/filters/group/group_filter.php', + 'ezcAuthenticationGroupOptions' => 'Authentication/options/group_options.php', + 'ezcAuthenticationHtpasswdFilter' => 'Authentication/filters/htpasswd/htpasswd_filter.php', + 'ezcAuthenticationHtpasswdOptions' => 'Authentication/options/htpasswd_options.php', + 'ezcAuthenticationIdCredentials' => 'Authentication/credentials/id_credentials.php', + 'ezcAuthenticationLdapFilter' => 'Authentication/filters/ldap/ldap_filter.php', + 'ezcAuthenticationLdapInfo' => 'Authentication/filters/ldap/ldap_info.php', + 'ezcAuthenticationLdapOptions' => 'Authentication/options/ldap_options.php', + 'ezcAuthenticationMath' => 'Authentication/math/math.php', + 'ezcAuthenticationOpenidAssociation' => 'Authentication/filters/openid/openid_association.php', + 'ezcAuthenticationOpenidFileStore' => 'Authentication/filters/openid/openid_file_store.php', + 'ezcAuthenticationOpenidFileStoreOptions' => 'Authentication/options/openid_file_store_options.php', + 'ezcAuthenticationOpenidFilter' => 'Authentication/filters/openid/openid_filter.php', + 'ezcAuthenticationOpenidOptions' => 'Authentication/options/openid_options.php', + 'ezcAuthenticationOptions' => 'Authentication/options/authentication_options.php', + 'ezcAuthenticationPasswordCredentials' => 'Authentication/credentials/password_credentials.php', + 'ezcAuthenticationSession' => 'Authentication/session/authentication_session.php', + 'ezcAuthenticationSessionOptions' => 'Authentication/options/session_options.php', + 'ezcAuthenticationStatus' => 'Authentication/status/authentication_status.php', + 'ezcAuthenticationTokenFilter' => 'Authentication/filters/token/token_filter.php', + 'ezcAuthenticationTokenOptions' => 'Authentication/options/token_options.php', + 'ezcAuthenticationTypekeyFilter' => 'Authentication/filters/typekey/typekey_filter.php', + 'ezcAuthenticationTypekeyOptions' => 'Authentication/options/typekey_options.php', + 'ezcAuthenticationUrl' => 'Authentication/url/url.php', +); +?> diff --git a/include/ezcomponents/autoload/authentication_database_autoload.php b/include/ezcomponents/autoload/authentication_database_autoload.php new file mode 100644 index 000000000..e7caf5bcd --- /dev/null +++ b/include/ezcomponents/autoload/authentication_database_autoload.php @@ -0,0 +1,17 @@ + 'AuthenticationDatabaseTiein/filters/database/database_filter.php', + 'ezcAuthenticationDatabaseInfo' => 'AuthenticationDatabaseTiein/filters/database/database_info.php', + 'ezcAuthenticationDatabaseOptions' => 'AuthenticationDatabaseTiein/options/database_options.php', +); +?> diff --git a/include/ezcomponents/autoload/authentication_openid_autoload.php b/include/ezcomponents/autoload/authentication_openid_autoload.php new file mode 100644 index 000000000..76060495f --- /dev/null +++ b/include/ezcomponents/autoload/authentication_openid_autoload.php @@ -0,0 +1,16 @@ + 'AuthenticationDatabaseTiein/filters/openid/openid_db_store.php', + 'ezcAuthenticationOpenidDbStoreOptions' => 'AuthenticationDatabaseTiein/options/openid_db_store_options.php', +); +?> diff --git a/include/ezcomponents/autoload/base_autoload.php b/include/ezcomponents/autoload/base_autoload.php new file mode 100644 index 000000000..0a03efae5 --- /dev/null +++ b/include/ezcomponents/autoload/base_autoload.php @@ -0,0 +1,41 @@ + 'Base/exceptions/exception.php', + 'ezcBaseFileException' => 'Base/exceptions/file_exception.php', + 'ezcBaseAutoloadException' => 'Base/exceptions/autoload.php', + 'ezcBaseDoubleClassRepositoryPrefixException' => 'Base/exceptions/double_class_repository_prefix.php', + 'ezcBaseExtensionNotFoundException' => 'Base/exceptions/extension_not_found.php', + 'ezcBaseFileIoException' => 'Base/exceptions/file_io.php', + 'ezcBaseFileNotFoundException' => 'Base/exceptions/file_not_found.php', + 'ezcBaseFilePermissionException' => 'Base/exceptions/file_permission.php', + 'ezcBaseInitCallbackConfiguredException' => 'Base/exceptions/init_callback_configured.php', + 'ezcBaseInitInvalidCallbackClassException' => 'Base/exceptions/invalid_callback_class.php', + 'ezcBaseInvalidParentClassException' => 'Base/exceptions/invalid_parent_class.php', + 'ezcBasePropertyNotFoundException' => 'Base/exceptions/property_not_found.php', + 'ezcBasePropertyPermissionException' => 'Base/exceptions/property_permission.php', + 'ezcBaseSettingNotFoundException' => 'Base/exceptions/setting_not_found.php', + 'ezcBaseSettingValueException' => 'Base/exceptions/setting_value.php', + 'ezcBaseValueException' => 'Base/exceptions/value.php', + 'ezcBaseWhateverException' => 'Base/exceptions/whatever.php', + 'ezcBaseOptions' => 'Base/options.php', + 'ezcBaseStruct' => 'Base/struct.php', + 'ezcBase' => 'Base/base.php', + 'ezcBaseAutoloadOptions' => 'Base/options/autoload.php', + 'ezcBaseConfigurationInitializer' => 'Base/interfaces/configuration_initializer.php', + 'ezcBaseFeatures' => 'Base/features.php', + 'ezcBaseFile' => 'Base/file.php', + 'ezcBaseInit' => 'Base/init.php', + 'ezcBasePersistable' => 'Base/interfaces/persistable.php', + 'ezcBaseRepositoryDirectory' => 'Base/structs/repository_directory.php', +); +?> diff --git a/include/ezcomponents/autoload/cache_autoload.php b/include/ezcomponents/autoload/cache_autoload.php new file mode 100644 index 000000000..9bb0efddc --- /dev/null +++ b/include/ezcomponents/autoload/cache_autoload.php @@ -0,0 +1,63 @@ + 'Cache/exceptions/exception.php', + 'ezcCacheApcException' => 'Cache/exceptions/apc_exception.php', + 'ezcCacheInvalidDataException' => 'Cache/exceptions/invalid_data.php', + 'ezcCacheInvalidIdException' => 'Cache/exceptions/invalid_id.php', + 'ezcCacheInvalidMetaDataException' => 'Cache/exceptions/invalid_meta_data.php', + 'ezcCacheInvalidStorageClassException' => 'Cache/exceptions/invalid_storage_class.php', + 'ezcCacheMemcacheException' => 'Cache/exceptions/memcache_exception.php', + 'ezcCacheStackIdAlreadyUsedException' => 'Cache/exceptions/stack_id_already_used.php', + 'ezcCacheStackStorageUsedTwiceException' => 'Cache/exceptions/stack_storage_used_twice.php', + 'ezcCacheStackUnderflowException' => 'Cache/exceptions/stack_underflow.php', + 'ezcCacheUsedLocationException' => 'Cache/exceptions/used_location.php', + 'ezcCacheStackMetaDataStorage' => 'Cache/interfaces/meta_data_storage.php', + 'ezcCacheStackableStorage' => 'Cache/interfaces/stackable_storage.php', + 'ezcCacheStorage' => 'Cache/storage.php', + 'ezcCacheStackMetaData' => 'Cache/interfaces/meta_data.php', + 'ezcCacheStackReplacementStrategy' => 'Cache/interfaces/replacement_strategy.php', + 'ezcCacheStorageMemory' => 'Cache/storage/memory.php', + 'ezcCacheMemoryBackend' => 'Cache/backends/memory_backend.php', + 'ezcCacheStackBaseMetaData' => 'Cache/interfaces/base_meta_data.php', + 'ezcCacheStackBaseReplacementStrategy' => 'Cache/interfaces/base_replacement_strategy.php', + 'ezcCacheStorageApc' => 'Cache/storage/apc.php', + 'ezcCacheStorageApcOptions' => 'Cache/options/storage_apc.php', + 'ezcCacheStorageFile' => 'Cache/storage/file.php', + 'ezcCacheStorageMemcache' => 'Cache/storage/memcache.php', + 'ezcCacheApcBackend' => 'Cache/backends/apc/apc_backend.php', + 'ezcCacheManager' => 'Cache/manager.php', + 'ezcCacheMemcacheBackend' => 'Cache/backends/memcache/memcache_backend.php', + 'ezcCacheMemoryVarStruct' => 'Cache/structs/memory_var.php', + 'ezcCacheStack' => 'Cache/stack.php', + 'ezcCacheStackConfigurator' => 'Cache/interfaces/stack_configurator.php', + 'ezcCacheStackLfuMetaData' => 'Cache/stack/lfu_meta_data.php', + 'ezcCacheStackLfuReplacementStrategy' => 'Cache/replacement_strategies/lfu.php', + 'ezcCacheStackLruMetaData' => 'Cache/stack/lru_meta_data.php', + 'ezcCacheStackLruReplacementStrategy' => 'Cache/replacement_strategies/lru.php', + 'ezcCacheStackOptions' => 'Cache/options/stack.php', + 'ezcCacheStackStorageConfiguration' => 'Cache/stack/storage_configuration.php', + 'ezcCacheStorageApcPlain' => 'Cache/storage/apc/plain.php', + 'ezcCacheStorageFileApcArray' => 'Cache/storage/apc/apc_array.php', + 'ezcCacheStorageFileApcArrayDataStruct' => 'Cache/structs/file_apc_array_data.php', + 'ezcCacheStorageFileApcArrayOptions' => 'Cache/options/storage_apc_array.php', + 'ezcCacheStorageFileArray' => 'Cache/storage/file/array.php', + 'ezcCacheStorageFileEvalArray' => 'Cache/storage/file/eval_array.php', + 'ezcCacheStorageFileOptions' => 'Cache/options/storage_file.php', + 'ezcCacheStorageFilePlain' => 'Cache/storage/file/plain.php', + 'ezcCacheStorageMemcacheOptions' => 'Cache/options/storage_memcache.php', + 'ezcCacheStorageMemcachePlain' => 'Cache/storage/memcache/plain.php', + 'ezcCacheStorageMemoryDataStruct' => 'Cache/structs/memory_data.php', + 'ezcCacheStorageMemoryRegisterStruct' => 'Cache/structs/memory_register.php', + 'ezcCacheStorageOptions' => 'Cache/options/storage.php', +); +?> diff --git a/include/ezcomponents/autoload/configuration_autoload.php b/include/ezcomponents/autoload/configuration_autoload.php new file mode 100644 index 000000000..08f464d67 --- /dev/null +++ b/include/ezcomponents/autoload/configuration_autoload.php @@ -0,0 +1,43 @@ + 'Configuration/exceptions/exception.php', + 'ezcConfigurationGroupExistsAlreadyException' => 'Configuration/exceptions/group_exists_already.php', + 'ezcConfigurationInvalidReaderClassException' => 'Configuration/exceptions/invalid_reader_class.php', + 'ezcConfigurationInvalidSuffixException' => 'Configuration/exceptions/invalid_suffix.php', + 'ezcConfigurationManagerNotInitializedException' => 'Configuration/exceptions/manager_no_init.php', + 'ezcConfigurationNoConfigException' => 'Configuration/exceptions/no_config.php', + 'ezcConfigurationNoConfigObjectException' => 'Configuration/exceptions/no_config_object.php', + 'ezcConfigurationParseErrorException' => 'Configuration/exceptions/parse_error.php', + 'ezcConfigurationReadFailedException' => 'Configuration/exceptions/read_failed.php', + 'ezcConfigurationSettingWrongTypeException' => 'Configuration/exceptions/setting_wrong_type.php', + 'ezcConfigurationSettingnameNotStringException' => 'Configuration/exceptions/settingname_not_string.php', + 'ezcConfigurationUnknownConfigException' => 'Configuration/exceptions/unknown_config.php', + 'ezcConfigurationUnknownGroupException' => 'Configuration/exceptions/unknown_group.php', + 'ezcConfigurationUnknownSettingException' => 'Configuration/exceptions/unknown_setting.php', + 'ezcConfigurationWriteFailedException' => 'Configuration/exceptions/write_failed.php', + 'ezcConfigurationReader' => 'Configuration/interfaces/reader.php', + 'ezcConfigurationWriter' => 'Configuration/interfaces/writer.php', + 'ezcConfigurationFileReader' => 'Configuration/file_reader.php', + 'ezcConfigurationFileWriter' => 'Configuration/file_writer.php', + 'ezcConfiguration' => 'Configuration/configuration.php', + 'ezcConfigurationArrayReader' => 'Configuration/array/array_reader.php', + 'ezcConfigurationArrayWriter' => 'Configuration/array/array_writer.php', + 'ezcConfigurationIniItem' => 'Configuration/structs/ini_item.php', + 'ezcConfigurationIniParser' => 'Configuration/ini/ini_parser.php', + 'ezcConfigurationIniReader' => 'Configuration/ini/ini_reader.php', + 'ezcConfigurationIniWriter' => 'Configuration/ini/ini_writer.php', + 'ezcConfigurationManager' => 'Configuration/configuration_manager.php', + 'ezcConfigurationValidationItem' => 'Configuration/structs/validation_item.php', + 'ezcConfigurationValidationResult' => 'Configuration/validation_result.php', +); +?> diff --git a/include/ezcomponents/autoload/console_autoload.php b/include/ezcomponents/autoload/console_autoload.php new file mode 100644 index 000000000..922b4abe2 --- /dev/null +++ b/include/ezcomponents/autoload/console_autoload.php @@ -0,0 +1,70 @@ + 'ConsoleTools/exceptions/exception.php', + 'ezcConsoleArgumentException' => 'ConsoleTools/exceptions/argument.php', + 'ezcConsoleOptionException' => 'ConsoleTools/exceptions/option.php', + 'ezcConsoleArgumentAlreadyRegisteredException' => 'ConsoleTools/exceptions/argument_already_registered.php', + 'ezcConsoleArgumentMandatoryViolationException' => 'ConsoleTools/exceptions/argument_mandatory_violation.php', + 'ezcConsoleArgumentTypeViolationException' => 'ConsoleTools/exceptions/argument_type_violation.php', + 'ezcConsoleInvalidOptionNameException' => 'ConsoleTools/exceptions/invalid_option_name.php', + 'ezcConsoleInvalidOutputTargetException' => 'ConsoleTools/exceptions/invalid_output_target.php', + 'ezcConsoleNoPositionStoredException' => 'ConsoleTools/exceptions/no_position_stored.php', + 'ezcConsoleNoValidDialogResultException' => 'ConsoleTools/exceptions/no_valid_dialog_result.php', + 'ezcConsoleOptionAlreadyRegisteredException' => 'ConsoleTools/exceptions/option_already_registered.php', + 'ezcConsoleOptionArgumentsViolationException' => 'ConsoleTools/exceptions/option_arguments_violation.php', + 'ezcConsoleOptionDependencyViolationException' => 'ConsoleTools/exceptions/option_dependency_violation.php', + 'ezcConsoleOptionExclusionViolationException' => 'ConsoleTools/exceptions/option_exclusion_violation.php', + 'ezcConsoleOptionMandatoryViolationException' => 'ConsoleTools/exceptions/option_mandatory_violation.php', + 'ezcConsoleOptionMissingValueException' => 'ConsoleTools/exceptions/option_missing_value.php', + 'ezcConsoleOptionNoAliasException' => 'ConsoleTools/exceptions/option_no_alias.php', + 'ezcConsoleOptionNotExistsException' => 'ConsoleTools/exceptions/option_not_exists.php', + 'ezcConsoleOptionStringNotWellformedException' => 'ConsoleTools/exceptions/option_string_not_wellformed.php', + 'ezcConsoleOptionTooManyValuesException' => 'ConsoleTools/exceptions/option_too_many_values.php', + 'ezcConsoleOptionTypeViolationException' => 'ConsoleTools/exceptions/option_type_violation.php', + 'ezcConsoleTooManyArgumentsException' => 'ConsoleTools/exceptions/argument_too_many.php', + 'ezcConsoleDialogValidator' => 'ConsoleTools/interfaces/dialog_validator.php', + 'ezcConsoleQuestionDialogValidator' => 'ConsoleTools/interfaces/question_dialog_validator.php', + 'ezcConsoleDialog' => 'ConsoleTools/interfaces/dialog.php', + 'ezcConsoleDialogOptions' => 'ConsoleTools/options/dialog.php', + 'ezcConsoleMenuDialogValidator' => 'ConsoleTools/interfaces/menu_dialog_validator.php', + 'ezcConsoleQuestionDialogCollectionValidator' => 'ConsoleTools/dialog/validators/question_dialog_collection.php', + 'ezcConsoleArgument' => 'ConsoleTools/input/argument.php', + 'ezcConsoleArguments' => 'ConsoleTools/input/arguments.php', + 'ezcConsoleDialogViewer' => 'ConsoleTools/dialog_viewer.php', + 'ezcConsoleInput' => 'ConsoleTools/input.php', + 'ezcConsoleMenuDialog' => 'ConsoleTools/dialog/menu_dialog.php', + 'ezcConsoleMenuDialogDefaultValidator' => 'ConsoleTools/dialog/validators/menu_dialog_default.php', + 'ezcConsoleMenuDialogOptions' => 'ConsoleTools/options/menu_dialog.php', + 'ezcConsoleOption' => 'ConsoleTools/input/option.php', + 'ezcConsoleOptionRule' => 'ConsoleTools/structs/option_rule.php', + 'ezcConsoleOutput' => 'ConsoleTools/output.php', + 'ezcConsoleOutputFormat' => 'ConsoleTools/structs/output_format.php', + 'ezcConsoleOutputFormats' => 'ConsoleTools/structs/output_formats.php', + 'ezcConsoleOutputOptions' => 'ConsoleTools/options/output.php', + 'ezcConsoleProgressMonitor' => 'ConsoleTools/progressmonitor.php', + 'ezcConsoleProgressMonitorOptions' => 'ConsoleTools/options/progressmonitor.php', + 'ezcConsoleProgressbar' => 'ConsoleTools/progressbar.php', + 'ezcConsoleProgressbarOptions' => 'ConsoleTools/options/progressbar.php', + 'ezcConsoleQuestionDialog' => 'ConsoleTools/dialog/question_dialog.php', + 'ezcConsoleQuestionDialogMappingValidator' => 'ConsoleTools/dialog/validators/question_dialog_mapping.php', + 'ezcConsoleQuestionDialogOptions' => 'ConsoleTools/options/question_dialog.php', + 'ezcConsoleQuestionDialogRegexValidator' => 'ConsoleTools/dialog/validators/question_dialog_regex.php', + 'ezcConsoleQuestionDialogTypeValidator' => 'ConsoleTools/dialog/validators/question_dialog_type.php', + 'ezcConsoleStatusbar' => 'ConsoleTools/statusbar.php', + 'ezcConsoleStatusbarOptions' => 'ConsoleTools/options/statusbar.php', + 'ezcConsoleTable' => 'ConsoleTools/table.php', + 'ezcConsoleTableCell' => 'ConsoleTools/table/cell.php', + 'ezcConsoleTableOptions' => 'ConsoleTools/options/table.php', + 'ezcConsoleTableRow' => 'ConsoleTools/table/row.php', +); +?> diff --git a/include/ezcomponents/autoload/db_autoload.php b/include/ezcomponents/autoload/db_autoload.php new file mode 100644 index 000000000..5f456ebbf --- /dev/null +++ b/include/ezcomponents/autoload/db_autoload.php @@ -0,0 +1,32 @@ + 'Database/exceptions/exception.php', + 'ezcDbHandlerNotFoundException' => 'Database/exceptions/handler_not_found.php', + 'ezcDbMissingParameterException' => 'Database/exceptions/missing_parameter.php', + 'ezcDbTransactionException' => 'Database/exceptions/transaction.php', + 'ezcDbHandler' => 'Database/handler.php', + 'ezcDbUtilities' => 'Database/sqlabstraction/utilities.php', + 'ezcDbFactory' => 'Database/factory.php', + 'ezcDbHandlerMssql' => 'Database/handlers/mssql.php', + 'ezcDbHandlerMysql' => 'Database/handlers/mysql.php', + 'ezcDbHandlerOracle' => 'Database/handlers/oracle.php', + 'ezcDbHandlerPgsql' => 'Database/handlers/pgsql.php', + 'ezcDbHandlerSqlite' => 'Database/handlers/sqlite.php', + 'ezcDbInstance' => 'Database/instance.php', + 'ezcDbMssqlOptions' => 'Database/options/identifiers.php', + 'ezcDbUtilitiesMysql' => 'Database/sqlabstraction/implementations/utilities_mysql.php', + 'ezcDbUtilitiesOracle' => 'Database/sqlabstraction/implementations/utilities_oracle.php', + 'ezcDbUtilitiesPgsql' => 'Database/sqlabstraction/implementations/utilities_pgsql.php', + 'ezcDbUtilitiesSqlite' => 'Database/sqlabstraction/implementations/utilities_sqlite.php', +); +?> diff --git a/include/ezcomponents/autoload/db_schema_autoload.php b/include/ezcomponents/autoload/db_schema_autoload.php new file mode 100644 index 000000000..c9e0d3a09 --- /dev/null +++ b/include/ezcomponents/autoload/db_schema_autoload.php @@ -0,0 +1,68 @@ + 'DatabaseSchema/exceptions/exception.php', + 'ezcDbSchemaDropAllColumnsException' => 'DatabaseSchema/exceptions/drop_all_columns_exception.php', + 'ezcDbSchemaInvalidDiffReaderClassException' => 'DatabaseSchema/exceptions/invalid_diff_reader_class.php', + 'ezcDbSchemaInvalidDiffWriterClassException' => 'DatabaseSchema/exceptions/invalid_diff_writer_class.php', + 'ezcDbSchemaInvalidReaderClassException' => 'DatabaseSchema/exceptions/invalid_reader_class.php', + 'ezcDbSchemaInvalidSchemaException' => 'DatabaseSchema/exceptions/invalid_schema.php', + 'ezcDbSchemaInvalidWriterClassException' => 'DatabaseSchema/exceptions/invalid_writer_class.php', + 'ezcDbSchemaSqliteDropFieldException' => 'DatabaseSchema/exceptions/sqlite_drop_field_exception.php', + 'ezcDbSchemaUnknownFormatException' => 'DatabaseSchema/exceptions/unknown_format.php', + 'ezcDbSchemaUnsupportedTypeException' => 'DatabaseSchema/exceptions/unsupported_type.php', + 'ezcDbSchemaDiffWriter' => 'DatabaseSchema/interfaces/schema_diff_writer.php', + 'ezcDbSchemaWriter' => 'DatabaseSchema/interfaces/schema_writer.php', + 'ezcDbSchemaDbWriter' => 'DatabaseSchema/interfaces/db_writer.php', + 'ezcDbSchemaDiffDbWriter' => 'DatabaseSchema/interfaces/db_diff_writer.php', + 'ezcDbSchemaDiffReader' => 'DatabaseSchema/interfaces/schema_diff_reader.php', + 'ezcDbSchemaReader' => 'DatabaseSchema/interfaces/schema_reader.php', + 'ezcDbSchemaCommonSqlWriter' => 'DatabaseSchema/handlers/common_sql_writer.php', + 'ezcDbSchemaDbReader' => 'DatabaseSchema/interfaces/db_reader.php', + 'ezcDbSchemaDiffFileReader' => 'DatabaseSchema/interfaces/file_diff_reader.php', + 'ezcDbSchemaDiffFileWriter' => 'DatabaseSchema/interfaces/file_diff_writer.php', + 'ezcDbSchemaFileReader' => 'DatabaseSchema/interfaces/file_reader.php', + 'ezcDbSchemaFileWriter' => 'DatabaseSchema/interfaces/file_writer.php', + 'XMLWriter' => 'DatabaseSchema/handlers/xml/xmlwritersubstitute.php', + 'ezcDbSchema' => 'DatabaseSchema/schema.php', + 'ezcDbSchemaAutoIncrementIndexValidator' => 'DatabaseSchema/validators/auto_increment_index.php', + 'ezcDbSchemaCommonSqlReader' => 'DatabaseSchema/handlers/common_sql_reader.php', + 'ezcDbSchemaComparator' => 'DatabaseSchema/comparator.php', + 'ezcDbSchemaDiff' => 'DatabaseSchema/schema_diff.php', + 'ezcDbSchemaField' => 'DatabaseSchema/structs/field.php', + 'ezcDbSchemaHandlerDataTransfer' => 'DatabaseSchema/handlers/data_transfer.php', + 'ezcDbSchemaHandlerManager' => 'DatabaseSchema/handler_manager.php', + 'ezcDbSchemaIndex' => 'DatabaseSchema/structs/index.php', + 'ezcDbSchemaIndexField' => 'DatabaseSchema/structs/index_field.php', + 'ezcDbSchemaIndexFieldsValidator' => 'DatabaseSchema/validators/index_fields.php', + 'ezcDbSchemaMysqlReader' => 'DatabaseSchema/handlers/mysql/reader.php', + 'ezcDbSchemaMysqlWriter' => 'DatabaseSchema/handlers/mysql/writer.php', + 'ezcDbSchemaOptions' => 'DatabaseSchema/options/schema.php', + 'ezcDbSchemaOracleReader' => 'DatabaseSchema/handlers/oracle/reader.php', + 'ezcDbSchemaOracleWriter' => 'DatabaseSchema/handlers/oracle/writer.php', + 'ezcDbSchemaPersistentClassWriter' => 'DatabaseSchema/handlers/persistent/class_writer.php', + 'ezcDbSchemaPersistentWriter' => 'DatabaseSchema/handlers/persistent/writer.php', + 'ezcDbSchemaPgsqlReader' => 'DatabaseSchema/handlers/pgsql/reader.php', + 'ezcDbSchemaPgsqlWriter' => 'DatabaseSchema/handlers/pgsql/writer.php', + 'ezcDbSchemaPhpArrayReader' => 'DatabaseSchema/handlers/php_array/reader.php', + 'ezcDbSchemaPhpArrayWriter' => 'DatabaseSchema/handlers/php_array/writer.php', + 'ezcDbSchemaSqliteReader' => 'DatabaseSchema/handlers/sqlite/reader.php', + 'ezcDbSchemaSqliteWriter' => 'DatabaseSchema/handlers/sqlite/writer.php', + 'ezcDbSchemaTable' => 'DatabaseSchema/structs/table.php', + 'ezcDbSchemaTableDiff' => 'DatabaseSchema/structs/table_diff.php', + 'ezcDbSchemaTypesValidator' => 'DatabaseSchema/validators/types.php', + 'ezcDbSchemaUniqueIndexNameValidator' => 'DatabaseSchema/validators/unique_index_name.php', + 'ezcDbSchemaValidator' => 'DatabaseSchema/validator.php', + 'ezcDbSchemaXmlReader' => 'DatabaseSchema/handlers/xml/reader.php', + 'ezcDbSchemaXmlWriter' => 'DatabaseSchema/handlers/xml/writer.php', +); +?> diff --git a/include/ezcomponents/autoload/debug_autoload.php b/include/ezcomponents/autoload/debug_autoload.php new file mode 100644 index 000000000..e7a993b81 --- /dev/null +++ b/include/ezcomponents/autoload/debug_autoload.php @@ -0,0 +1,30 @@ + 'Debug/exceptions/exception.php', + 'ezcDebugOperationNotPermittedException' => 'Debug/exceptions/operation_not_permitted.php', + 'ezcDebugOutputFormatter' => 'Debug/interfaces/formatter.php', + 'ezcDebugStacktraceIterator' => 'Debug/interfaces/stacktrace_iterator.php', + 'ezcDebug' => 'Debug/debug.php', + 'ezcDebugHtmlFormatter' => 'Debug/formatters/html_formatter.php', + 'ezcDebugMemoryWriter' => 'Debug/writers/memory_writer.php', + 'ezcDebugMessage' => 'Debug/debug_message.php', + 'ezcDebugOptions' => 'Debug/options.php', + 'ezcDebugPhpStacktraceIterator' => 'Debug/stacktrace/php_iterator.php', + 'ezcDebugStructure' => 'Debug/structs/debug_structure.php', + 'ezcDebugSwitchTimerStruct' => 'Debug/structs/switch_timer.php', + 'ezcDebugTimer' => 'Debug/debug_timer.php', + 'ezcDebugTimerStruct' => 'Debug/structs/timer.php', + 'ezcDebugVariableDumpTool' => 'Debug/tools/dump.php', + 'ezcDebugXdebugStacktraceIterator' => 'Debug/stacktrace/xdebug_iterator.php', +); +?> diff --git a/include/ezcomponents/autoload/document_autoload.php b/include/ezcomponents/autoload/document_autoload.php new file mode 100644 index 000000000..d54a405dd --- /dev/null +++ b/include/ezcomponents/autoload/document_autoload.php @@ -0,0 +1,108 @@ + 'Document/exceptions/exception.php', + 'ezcDocumentErrnousXmlException' => 'Document/exceptions/errnous_xml.php', + 'ezcDocumentMissingVisitorException' => 'Document/exceptions/missing_visitor.php', + 'ezcDocumentParserException' => 'Document/exceptions/parser.php', + 'ezcDocumentRstTokenizerException' => 'Document/exceptions/rst_tokenizer.php', + 'ezcDocumentVisitException' => 'Document/exceptions/visitor.php', + 'ezcDocument' => 'Document/interfaces/document.php', + 'ezcDocumentConverter' => 'Document/interfaces/converter.php', + 'ezcDocumentOptions' => 'Document/options/document.php', + 'ezcDocumentRstDirective' => 'Document/document/rst/directive.php', + 'ezcDocumentRstNode' => 'Document/document/rst/node.php', + 'ezcDocumentRstVisitor' => 'Document/document/rst/visitor.php', + 'ezcDocumentRstXhtmlDirective' => 'Document/interfaces/rst_xhtml_directive.php', + 'ezcDocumentValidation' => 'Document/interfaces/validation.php', + 'ezcDocumentConverterOptions' => 'Document/options/converter.php', + 'ezcDocumentParser' => 'Document/interfaces/parser.php', + 'ezcDocumentRstBlockNode' => 'Document/document/rst/nodes/block.php', + 'ezcDocumentRstImageDirective' => 'Document/document/rst/directive/image.php', + 'ezcDocumentRstLinkNode' => 'Document/document/rst/nodes/link.php', + 'ezcDocumentRstMarkupNode' => 'Document/document/rst/nodes/markup.php', + 'ezcDocumentRstXhtmlVisitor' => 'Document/document/rst/visitor/xhtml.php', + 'ezcDocumentXhtmlConversion' => 'Document/interfaces/conversion_xhtml.php', + 'ezcDocumentXmlBase' => 'Document/document/xml_base.php', + 'ezcDocumentXmlOptions' => 'Document/options/document_xml.php', + 'ezcDocumentXsltConverter' => 'Document/converters/xslt.php', + 'ezcDocumentDocbook' => 'Document/document/xml/docbook.php', + 'ezcDocumentDocbookOptions' => 'Document/options/document_docbook.php', + 'ezcDocumentEzp3ToEzp4Converter' => 'Document/converters/xslt/ezp3_ezp4.php', + 'ezcDocumentEzp3ToEzp4ConverterOptions' => 'Document/options/converter_ezp3_ezp4.php', + 'ezcDocumentEzp3Xml' => 'Document/document/xml/ezp3.php', + 'ezcDocumentEzp3XmlOptions' => 'Document/options/document_ezp3.php', + 'ezcDocumentEzp4Xml' => 'Document/document/xml/ezp4.php', + 'ezcDocumentEzp4XmlOptions' => 'Document/options/document_ezp4.php', + 'ezcDocumentHtmlConversion' => 'Document/interfaces/conversions/html.php', + 'ezcDocumentManager' => 'Document/document_manager.php', + 'ezcDocumentParserOptions' => 'Document/options/document_parser.php', + 'ezcDocumentRelaxNgValidator' => 'Document/validator/realxng.php', + 'ezcDocumentRst' => 'Document/document/rst.php', + 'ezcDocumentRstAnonymousLinkNode' => 'Document/document/rst/nodes/link_anonymous.php', + 'ezcDocumentRstAnonymousReferenceNode' => 'Document/document/rst/nodes/anon_reference.php', + 'ezcDocumentRstAttentionDirective' => 'Document/document/rst/directive/attention.php', + 'ezcDocumentRstBlockquoteAnnotationNode' => 'Document/document/rst/nodes/blockquote_annotation.php', + 'ezcDocumentRstBlockquoteNode' => 'Document/document/rst/nodes/blockquote.php', + 'ezcDocumentRstBulletListListNode' => 'Document/document/rst/nodes/bullet_list_list.php', + 'ezcDocumentRstBulletListNode' => 'Document/document/rst/nodes/bullet_list.php', + 'ezcDocumentRstCommentNode' => 'Document/document/rst/nodes/comment.php', + 'ezcDocumentRstContentsDirective' => 'Document/document/rst/directive/contents.php', + 'ezcDocumentRstDangerDirective' => 'Document/document/rst/directive/danger.php', + 'ezcDocumentRstDefinitionListListNode' => 'Document/document/rst/nodes/definition_list_list.php', + 'ezcDocumentRstDefinitionListNode' => 'Document/document/rst/nodes/definition_list.php', + 'ezcDocumentRstDirectiveNode' => 'Document/document/rst/nodes/directive.php', + 'ezcDocumentRstDocbookVisitor' => 'Document/document/rst/visitor/docbook.php', + 'ezcDocumentRstDocumentNode' => 'Document/document/rst/nodes/document.php', + 'ezcDocumentRstEnumeratedListListNode' => 'Document/document/rst/nodes/enumerated_list_list.php', + 'ezcDocumentRstEnumeratedListNode' => 'Document/document/rst/nodes/enumerated_list.php', + 'ezcDocumentRstExternalReferenceNode' => 'Document/document/rst/nodes/link_reference.php', + 'ezcDocumentRstFieldListNode' => 'Document/document/rst/nodes/field_list.php', + 'ezcDocumentRstFigureDirective' => 'Document/document/rst/directive/figure.php', + 'ezcDocumentRstFootnoteNode' => 'Document/document/rst/nodes/footnote.php', + 'ezcDocumentRstIncludeDirective' => 'Document/document/rst/directive/include.php', + 'ezcDocumentRstLineBlockLineNode' => 'Document/document/rst/nodes/line_block_line.php', + 'ezcDocumentRstLineBlockNode' => 'Document/document/rst/nodes/line_block.php', + 'ezcDocumentRstLiteralBlockNode' => 'Document/document/rst/nodes/literal_block.php', + 'ezcDocumentRstLiteralNode' => 'Document/document/rst/nodes/literal.php', + 'ezcDocumentRstMarkupEmphasisNode' => 'Document/document/rst/nodes/markup_emphasis.php', + 'ezcDocumentRstMarkupInlineLiteralNode' => 'Document/document/rst/nodes/markup_inline_literal.php', + 'ezcDocumentRstMarkupInterpretedTextNode' => 'Document/document/rst/nodes/markup_interpreted_text.php', + 'ezcDocumentRstMarkupStrongEmphasisNode' => 'Document/document/rst/nodes/markup_strong_emphasis.php', + 'ezcDocumentRstMarkupSubstitutionNode' => 'Document/document/rst/nodes/markup_substitution.php', + 'ezcDocumentRstNamedReferenceNode' => 'Document/document/rst/nodes/named_reference.php', + 'ezcDocumentRstNoteDirective' => 'Document/document/rst/directive/note.php', + 'ezcDocumentRstNoticeDirective' => 'Document/document/rst/directive/notice.php', + 'ezcDocumentRstOptions' => 'Document/options/document_rst.php', + 'ezcDocumentRstParagraphNode' => 'Document/document/rst/nodes/paragraph.php', + 'ezcDocumentRstParser' => 'Document/document/rst/parser.php', + 'ezcDocumentRstReferenceNode' => 'Document/document/rst/nodes/reference.php', + 'ezcDocumentRstSectionNode' => 'Document/document/rst/nodes/section.php', + 'ezcDocumentRstSubstitutionNode' => 'Document/document/rst/nodes/substitution.php', + 'ezcDocumentRstTableBodyNode' => 'Document/document/rst/nodes/table_body.php', + 'ezcDocumentRstTableCellNode' => 'Document/document/rst/nodes/table_cell.php', + 'ezcDocumentRstTableHeadNode' => 'Document/document/rst/nodes/table_head.php', + 'ezcDocumentRstTableNode' => 'Document/document/rst/nodes/table.php', + 'ezcDocumentRstTableRowNode' => 'Document/document/rst/nodes/table_row.php', + 'ezcDocumentRstTargetNode' => 'Document/document/rst/nodes/target.php', + 'ezcDocumentRstTextLineNode' => 'Document/document/rst/nodes/text_line.php', + 'ezcDocumentRstTitleNode' => 'Document/document/rst/nodes/title.php', + 'ezcDocumentRstToken' => 'Document/document/rst/token.php', + 'ezcDocumentRstTokenizer' => 'Document/document/rst/tokenizer.php', + 'ezcDocumentRstTransitionNode' => 'Document/document/rst/nodes/transition.php', + 'ezcDocumentRstWarningDirective' => 'Document/document/rst/directive/warning.php', + 'ezcDocumentRstXhtmlBodyVisitor' => 'Document/document/rst/visitor/xhtml_body.php', + 'ezcDocumentXhtml' => 'Document/document/xml/xhtml.php', + 'ezcDocumentXhtmlOptions' => 'Document/options/document_xhtml.php', + 'ezcDocumentXhtmlToDocbookConverter' => 'Document/converters/xslt/xhtml_docbook.php', +); +?> diff --git a/include/ezcomponents/autoload/execution_autoload.php b/include/ezcomponents/autoload/execution_autoload.php new file mode 100644 index 000000000..f7fe43975 --- /dev/null +++ b/include/ezcomponents/autoload/execution_autoload.php @@ -0,0 +1,22 @@ + 'Execution/exceptions/exception.php', + 'ezcExecutionAlreadyInitializedException' => 'Execution/exceptions/already_initialized.php', + 'ezcExecutionInvalidCallbackException' => 'Execution/exceptions/invalid_callback.php', + 'ezcExecutionNotInitializedException' => 'Execution/exceptions/not_initialized.php', + 'ezcExecutionWrongClassException' => 'Execution/exceptions/wrong_class.php', + 'ezcExecutionErrorHandler' => 'Execution/interfaces/execution_handler.php', + 'ezcExecution' => 'Execution/execution.php', + 'ezcExecutionBasicErrorHandler' => 'Execution/handlers/basic_handler.php', +); +?> diff --git a/include/ezcomponents/autoload/feed_autoload.php b/include/ezcomponents/autoload/feed_autoload.php new file mode 100644 index 000000000..2c00b59a5 --- /dev/null +++ b/include/ezcomponents/autoload/feed_autoload.php @@ -0,0 +1,52 @@ + 'Feed/exceptions/exception.php', + 'ezcFeedAtLeastOneItemDataRequiredException' => 'Feed/exceptions/one_item_data_required.php', + 'ezcFeedOnlyOneValueAllowedException' => 'Feed/exceptions/only_one_value_allowed.php', + 'ezcFeedParseErrorException' => 'Feed/exceptions/parse_error.php', + 'ezcFeedRequiredMetaDataMissingException' => 'Feed/exceptions/meta_data_missing.php', + 'ezcFeedUndefinedModuleException' => 'Feed/exceptions/undefined_module.php', + 'ezcFeedUnsupportedElementException' => 'Feed/exceptions/unsupported_element.php', + 'ezcFeedUnsupportedModuleException' => 'Feed/exceptions/unsupported_module.php', + 'ezcFeedUnsupportedTypeException' => 'Feed/exceptions/unsupported_type.php', + 'ezcFeedElement' => 'Feed/interfaces/element.php', + 'ezcFeedModule' => 'Feed/interfaces/module.php', + 'ezcFeedParser' => 'Feed/interfaces/parser.php', + 'ezcFeedProcessor' => 'Feed/interfaces/processor.php', + 'ezcFeedTextElement' => 'Feed/structs/text.php', + 'ezcFeed' => 'Feed/feed.php', + 'ezcFeedAtom' => 'Feed/processors/atom.php', + 'ezcFeedCategoryElement' => 'Feed/structs/category.php', + 'ezcFeedCloudElement' => 'Feed/structs/cloud.php', + 'ezcFeedContentElement' => 'Feed/structs/content.php', + 'ezcFeedContentModule' => 'Feed/modules/content_module.php', + 'ezcFeedCreativeCommonsModule' => 'Feed/modules/creativecommons_module.php', + 'ezcFeedDateElement' => 'Feed/structs/date.php', + 'ezcFeedDublinCoreModule' => 'Feed/modules/dublincore_module.php', + 'ezcFeedEnclosureElement' => 'Feed/structs/enclosure.php', + 'ezcFeedEntryElement' => 'Feed/structs/entry.php', + 'ezcFeedGeneratorElement' => 'Feed/structs/generator.php', + 'ezcFeedGeoModule' => 'Feed/modules/geo_module.php', + 'ezcFeedITunesModule' => 'Feed/modules/itunes_module.php', + 'ezcFeedIdElement' => 'Feed/structs/id.php', + 'ezcFeedImageElement' => 'Feed/structs/image.php', + 'ezcFeedLinkElement' => 'Feed/structs/link.php', + 'ezcFeedPersonElement' => 'Feed/structs/person.php', + 'ezcFeedRss1' => 'Feed/processors/rss1.php', + 'ezcFeedRss2' => 'Feed/processors/rss2.php', + 'ezcFeedSkipDaysElement' => 'Feed/structs/skip_days.php', + 'ezcFeedSkipHoursElement' => 'Feed/structs/skip_hours.php', + 'ezcFeedSourceElement' => 'Feed/structs/source.php', + 'ezcFeedTextInputElement' => 'Feed/structs/text_input.php', +); +?> diff --git a/include/ezcomponents/autoload/file_autoload.php b/include/ezcomponents/autoload/file_autoload.php new file mode 100644 index 000000000..531250418 --- /dev/null +++ b/include/ezcomponents/autoload/file_autoload.php @@ -0,0 +1,15 @@ + 'File/file.php', +); +?> diff --git a/include/ezcomponents/autoload/graph_autoload.php b/include/ezcomponents/autoload/graph_autoload.php new file mode 100644 index 000000000..ac074428f --- /dev/null +++ b/include/ezcomponents/autoload/graph_autoload.php @@ -0,0 +1,124 @@ + 'Graph/exceptions/exception.php', + 'ezcGraphDatasetAverageInvalidKeysException' => 'Graph/exceptions/invalid_keys.php', + 'ezcGraphErrorParsingDateException' => 'Graph/exceptions/date_parsing.php', + 'ezcGraphFlashBitmapTypeException' => 'Graph/exceptions/flash_bitmap_type.php', + 'ezcGraphFontRenderingException' => 'Graph/exceptions/font_rendering.php', + 'ezcGraphGdDriverUnsupportedImageTypeException' => 'Graph/exceptions/unsupported_image_type.php', + 'ezcGraphInvalidArrayDataSourceException' => 'Graph/exceptions/invalid_data_source.php', + 'ezcGraphInvalidAssignementException' => 'Graph/exceptions/invalid_assignement.php', + 'ezcGraphInvalidDataException' => 'Graph/exceptions/invalid_data.php', + 'ezcGraphInvalidDisplayTypeException' => 'Graph/exceptions/invalid_display_type.php', + 'ezcGraphInvalidImageFileException' => 'Graph/exceptions/invalid_image_file.php', + 'ezcGraphMatrixInvalidDimensionsException' => 'Graph/exceptions/invalid_dimensions.php', + 'ezcGraphMatrixOutOfBoundingsException' => 'Graph/exceptions/out_of_boundings.php', + 'ezcGraphNoDataException' => 'Graph/exceptions/no_data.php', + 'ezcGraphNoSuchDataException' => 'Graph/exceptions/no_such_data.php', + 'ezcGraphNoSuchDataSetException' => 'Graph/exceptions/no_such_dataset.php', + 'ezcGraphNoSuchElementException' => 'Graph/exceptions/no_such_element.php', + 'ezcGraphOutOfLogithmicalBoundingsException' => 'Graph/exceptions/out_of_logarithmical_boundings.php', + 'ezcGraphReducementFailedException' => 'Graph/exceptions/reducement_failed.php', + 'ezcGraphSvgDriverInvalidIdException' => 'Graph/exceptions/invalid_id.php', + 'ezcGraphTooManyDataSetsExceptions' => 'Graph/exceptions/too_many_datasets.php', + 'ezcGraphToolsIncompatibleDriverException' => 'Graph/exceptions/incompatible_driver.php', + 'ezcGraphToolsNotRenderedException' => 'Graph/exceptions/not_rendered.php', + 'ezcGraphUnknownColorDefinitionException' => 'Graph/exceptions/unknown_color_definition.php', + 'ezcGraphUnknownFontTypeException' => 'Graph/exceptions/font_type.php', + 'ezcGraphUnregularStepsException' => 'Graph/exceptions/unregular_steps.php', + 'ezcGraphChart' => 'Graph/interfaces/chart.php', + 'ezcGraphChartElement' => 'Graph/interfaces/element.php', + 'ezcGraphChartOptions' => 'Graph/options/chart.php', + 'ezcGraphMatrix' => 'Graph/math/matrix.php', + 'ezcGraphAxisLabelRenderer' => 'Graph/interfaces/axis_label_renderer.php', + 'ezcGraphChartDataContainer' => 'Graph/data_container/base.php', + 'ezcGraphChartElementAxis' => 'Graph/element/axis.php', + 'ezcGraphColor' => 'Graph/colors/color.php', + 'ezcGraphCoordinate' => 'Graph/structs/coordinate.php', + 'ezcGraphDataSet' => 'Graph/datasets/base.php', + 'ezcGraphDataSetProperty' => 'Graph/interfaces/dataset_property.php', + 'ezcGraphDriver' => 'Graph/interfaces/driver.php', + 'ezcGraphDriverOptions' => 'Graph/options/driver.php', + 'ezcGraphLineChart' => 'Graph/charts/line.php', + 'ezcGraphOdometerRenderer' => 'Graph/interfaces/odometer_renderer.php', + 'ezcGraphPalette' => 'Graph/interfaces/palette.php', + 'ezcGraphRadarRenderer' => 'Graph/interfaces/radar_renderer.php', + 'ezcGraphRenderer' => 'Graph/interfaces/renderer.php', + 'ezcGraphRendererOptions' => 'Graph/options/renderer.php', + 'ezcGraphStackedBarsRenderer' => 'Graph/interfaces/stacked_bar_renderer.php', + 'ezcGraphTransformation' => 'Graph/math/transformation.php', + 'ezcGraph' => 'Graph/graph.php', + 'ezcGraphArrayDataSet' => 'Graph/datasets/array.php', + 'ezcGraphAxisBoxedLabelRenderer' => 'Graph/renderer/axis_label_boxed.php', + 'ezcGraphAxisCenteredLabelRenderer' => 'Graph/renderer/axis_label_centered.php', + 'ezcGraphAxisContainer' => 'Graph/axis/container.php', + 'ezcGraphAxisExactLabelRenderer' => 'Graph/renderer/axis_label_exact.php', + 'ezcGraphAxisNoLabelRenderer' => 'Graph/renderer/axis_label_none.php', + 'ezcGraphAxisRadarLabelRenderer' => 'Graph/renderer/axis_label_radar.php', + 'ezcGraphAxisRotatedLabelRenderer' => 'Graph/renderer/axis_label_rotated.php', + 'ezcGraphAxisStep' => 'Graph/structs/step.php', + 'ezcGraphBarChart' => 'Graph/charts/bar.php', + 'ezcGraphBoundings' => 'Graph/math/boundings.php', + 'ezcGraphCairoDriver' => 'Graph/driver/cairo.php', + 'ezcGraphCairoDriverOptions' => 'Graph/options/cairo_driver.php', + 'ezcGraphChartElementBackground' => 'Graph/element/background.php', + 'ezcGraphChartElementDateAxis' => 'Graph/axis/date.php', + 'ezcGraphChartElementLabeledAxis' => 'Graph/axis/labeled.php', + 'ezcGraphChartElementLegend' => 'Graph/element/legend.php', + 'ezcGraphChartElementLogarithmicalAxis' => 'Graph/axis/logarithmic.php', + 'ezcGraphChartElementNumericAxis' => 'Graph/axis/numeric.php', + 'ezcGraphChartElementText' => 'Graph/element/text.php', + 'ezcGraphChartSingleDataContainer' => 'Graph/data_container/single.php', + 'ezcGraphContext' => 'Graph/structs/context.php', + 'ezcGraphDataSetAveragePolynom' => 'Graph/datasets/average.php', + 'ezcGraphDataSetAxisProperty' => 'Graph/datasets/property/axis.php', + 'ezcGraphDataSetBooleanProperty' => 'Graph/datasets/property/boolean.php', + 'ezcGraphDataSetColorProperty' => 'Graph/datasets/property/color.php', + 'ezcGraphDataSetIntProperty' => 'Graph/datasets/property/integer.php', + 'ezcGraphDataSetStringProperty' => 'Graph/datasets/property/string.php', + 'ezcGraphFlashDriver' => 'Graph/driver/flash.php', + 'ezcGraphFlashDriverOptions' => 'Graph/options/flash_driver.php', + 'ezcGraphFontOptions' => 'Graph/options/font.php', + 'ezcGraphGdDriver' => 'Graph/driver/gd.php', + 'ezcGraphGdDriverOptions' => 'Graph/options/gd_driver.php', + 'ezcGraphLineChartOptions' => 'Graph/options/line_chart.php', + 'ezcGraphLinearGradient' => 'Graph/colors/linear_gradient.php', + 'ezcGraphNumericDataSet' => 'Graph/datasets/numeric.php', + 'ezcGraphOdometerChart' => 'Graph/charts/odometer.php', + 'ezcGraphOdometerChartOptions' => 'Graph/options/odometer_chart.php', + 'ezcGraphPaletteBlack' => 'Graph/palette/black.php', + 'ezcGraphPaletteEz' => 'Graph/palette/ez.php', + 'ezcGraphPaletteEzBlue' => 'Graph/palette/ez_blue.php', + 'ezcGraphPaletteEzGreen' => 'Graph/palette/ez_green.php', + 'ezcGraphPaletteEzRed' => 'Graph/palette/ez_red.php', + 'ezcGraphPaletteTango' => 'Graph/palette/tango.php', + 'ezcGraphPieChart' => 'Graph/charts/pie.php', + 'ezcGraphPieChartOptions' => 'Graph/options/pie_chart.php', + 'ezcGraphPolynom' => 'Graph/math/polynom.php', + 'ezcGraphRadarChart' => 'Graph/charts/radar.php', + 'ezcGraphRadarChartOptions' => 'Graph/options/radar_chart.php', + 'ezcGraphRadialGradient' => 'Graph/colors/radial_gradient.php', + 'ezcGraphRenderer2d' => 'Graph/renderer/2d.php', + 'ezcGraphRenderer2dOptions' => 'Graph/options/renderer_2d.php', + 'ezcGraphRenderer3d' => 'Graph/renderer/3d.php', + 'ezcGraphRenderer3dOptions' => 'Graph/options/renderer_3d.php', + 'ezcGraphRotation' => 'Graph/math/rotation.php', + 'ezcGraphSvgDriver' => 'Graph/driver/svg.php', + 'ezcGraphSvgDriverOptions' => 'Graph/options/svg_driver.php', + 'ezcGraphSvgFont' => 'Graph/driver/svg_font.php', + 'ezcGraphTools' => 'Graph/tools.php', + 'ezcGraphTranslation' => 'Graph/math/translation.php', + 'ezcGraphVector' => 'Graph/math/vector.php', + 'ezcGraphVerboseDriver' => 'Graph/driver/verbose.php', +); +?> diff --git a/include/ezcomponents/autoload/graph_database_autoload.php b/include/ezcomponents/autoload/graph_database_autoload.php new file mode 100644 index 000000000..26308ca38 --- /dev/null +++ b/include/ezcomponents/autoload/graph_database_autoload.php @@ -0,0 +1,19 @@ + 'GraphDatabaseTiein/exceptions/exception.php', + 'ezcGraphDatabaseMissingColumnException' => 'GraphDatabaseTiein/exceptions/missing_column.php', + 'ezcGraphDatabaseStatementNotExecutedException' => 'GraphDatabaseTiein/exceptions/statement_not_executed.php', + 'ezcGraphDatabaseTooManyColumnsException' => 'GraphDatabaseTiein/exceptions/too_many_columns.php', + 'ezcGraphDatabaseDataSet' => 'GraphDatabaseTiein/dataset.php', +); +?> diff --git a/include/ezcomponents/autoload/image_analyzer_autoload.php b/include/ezcomponents/autoload/image_analyzer_autoload.php new file mode 100644 index 000000000..12b965d6a --- /dev/null +++ b/include/ezcomponents/autoload/image_analyzer_autoload.php @@ -0,0 +1,22 @@ + 'ImageAnalysis/exceptions/exception.php', + 'ezcImageAnalyzerFileNotProcessableException' => 'ImageAnalysis/exceptions/file_not_processable.php', + 'ezcImageAnalyzerInvalidHandlerException' => 'ImageAnalysis/exceptions/invalid_handler.php', + 'ezcImageAnalyzerHandler' => 'ImageAnalysis/interfaces/handler.php', + 'ezcImageAnalyzer' => 'ImageAnalysis/analyzer.php', + 'ezcImageAnalyzerData' => 'ImageAnalysis/structs/analyzer_data.php', + 'ezcImageAnalyzerImagemagickHandler' => 'ImageAnalysis/handlers/imagemagick.php', + 'ezcImageAnalyzerPhpHandler' => 'ImageAnalysis/handlers/php.php', +); +?> diff --git a/include/ezcomponents/autoload/image_autoload.php b/include/ezcomponents/autoload/image_autoload.php new file mode 100644 index 000000000..9bc633424 --- /dev/null +++ b/include/ezcomponents/autoload/image_autoload.php @@ -0,0 +1,45 @@ + 'ImageConversion/exceptions/exception.php', + 'ezcImageFileNameInvalidException' => 'ImageConversion/exceptions/file_name_invalid.php', + 'ezcImageFileNotProcessableException' => 'ImageConversion/exceptions/file_not_processable.php', + 'ezcImageFilterFailedException' => 'ImageConversion/exceptions/filter_failed.php', + 'ezcImageFilterNotAvailableException' => 'ImageConversion/exceptions/filter_not_available.php', + 'ezcImageHandlerNotAvailableException' => 'ImageConversion/exceptions/handler_not_available.php', + 'ezcImageHandlerSettingsInvalidException' => 'ImageConversion/exceptions/handler_settings_invalid.php', + 'ezcImageInvalidFilterParameterException' => 'ImageConversion/exceptions/invalid_filter_parameter.php', + 'ezcImageInvalidReferenceException' => 'ImageConversion/exceptions/invalid_reference.php', + 'ezcImageMimeTypeUnsupportedException' => 'ImageConversion/exceptions/mime_type_unsupported.php', + 'ezcImageMissingFilterParameterException' => 'ImageConversion/exceptions/missing_filter_parameter.php', + 'ezcImageTransformationAlreadyExistsException' => 'ImageConversion/exceptions/transformation_already_exists.php', + 'ezcImageTransformationException' => 'ImageConversion/exceptions/transformation.php', + 'ezcImageTransformationNotAvailableException' => 'ImageConversion/exceptions/transformation_not_available.php', + 'ezcImageHandler' => 'ImageConversion/interfaces/handler.php', + 'ezcImageMethodcallHandler' => 'ImageConversion/interfaces/methodcall_handler.php', + 'ezcImageColorspaceFilters' => 'ImageConversion/interfaces/colorspace.php', + 'ezcImageEffectFilters' => 'ImageConversion/interfaces/effect.php', + 'ezcImageGdBaseHandler' => 'ImageConversion/handlers/gd_base.php', + 'ezcImageGeometryFilters' => 'ImageConversion/interfaces/geometry.php', + 'ezcImageImagemagickBaseHandler' => 'ImageConversion/handlers/imagemagick_base.php', + 'ezcImageThumbnailFilters' => 'ImageConversion/interfaces/thumbnail.php', + 'ezcImageWatermarkFilters' => 'ImageConversion/interfaces/watermark.php', + 'ezcImageConverter' => 'ImageConversion/converter.php', + 'ezcImageConverterSettings' => 'ImageConversion/structs/converter_settings.php', + 'ezcImageFilter' => 'ImageConversion/structs/filter.php', + 'ezcImageGdHandler' => 'ImageConversion/handlers/gd.php', + 'ezcImageHandlerSettings' => 'ImageConversion/structs/handler_settings.php', + 'ezcImageImagemagickHandler' => 'ImageConversion/handlers/imagemagick.php', + 'ezcImageSaveOptions' => 'ImageConversion/options/save_options.php', + 'ezcImageTransformation' => 'ImageConversion/transformation.php', +); +?> diff --git a/include/ezcomponents/autoload/input_autoload.php b/include/ezcomponents/autoload/input_autoload.php new file mode 100644 index 000000000..a50f41705 --- /dev/null +++ b/include/ezcomponents/autoload/input_autoload.php @@ -0,0 +1,25 @@ + 'UserInput/exceptions/exception.php', + 'ezcInputFormFieldNotFoundException' => 'UserInput/exceptions/field_not_found.php', + 'ezcInputFormInvalidDefinitionException' => 'UserInput/exceptions/invalid_definition.php', + 'ezcInputFormNoValidDataException' => 'UserInput/exceptions/no_valid_data.php', + 'ezcInputFormUnknownFieldException' => 'UserInput/exceptions/unknown_field.php', + 'ezcInputFormValidDataAvailableException' => 'UserInput/exceptions/valid_data_available.php', + 'ezcInputFormVariableMissingException' => 'UserInput/exceptions/input_variable_missing.php', + 'ezcInputFormWrongInputSourceException' => 'UserInput/exceptions/wrong_input_source.php', + 'ezcInputFilter' => 'UserInput/input_filter.php', + 'ezcInputForm' => 'UserInput/input_form.php', + 'ezcInputFormDefinitionElement' => 'UserInput/structs/definition_element.php', +); +?> diff --git a/include/ezcomponents/autoload/log_autoload.php b/include/ezcomponents/autoload/log_autoload.php new file mode 100644 index 000000000..ebe5fb9a9 --- /dev/null +++ b/include/ezcomponents/autoload/log_autoload.php @@ -0,0 +1,27 @@ + 'EventLog/exceptions/writer_exception.php', + 'ezcLogWrongSeverityException' => 'EventLog/exceptions/wrong_severity.php', + 'ezcLogWriter' => 'EventLog/interfaces/writer.php', + 'ezcLogFileWriter' => 'EventLog/writers/writer_file.php', + 'ezcLogMapper' => 'EventLog/interfaces/mapper.php', + 'ezcLog' => 'EventLog/log.php', + 'ezcLogContext' => 'EventLog/context.php', + 'ezcLogFilter' => 'EventLog/structs/log_filter.php', + 'ezcLogFilterRule' => 'EventLog/mapper/filter_rule.php', + 'ezcLogFilterSet' => 'EventLog/mapper/filterset.php', + 'ezcLogMessage' => 'EventLog/log_message.php', + 'ezcLogSyslogWriter' => 'EventLog/writers/writer_syslog.php', + 'ezcLogUnixFileWriter' => 'EventLog/writers/writer_unix_file.php', +); +?> diff --git a/include/ezcomponents/autoload/log_database_autoload.php b/include/ezcomponents/autoload/log_database_autoload.php new file mode 100644 index 000000000..d88bf7a8e --- /dev/null +++ b/include/ezcomponents/autoload/log_database_autoload.php @@ -0,0 +1,15 @@ + 'EventLogDatabaseTiein/writers/writer_database.php', +); +?> diff --git a/include/ezcomponents/autoload/mail_autoload.php b/include/ezcomponents/autoload/mail_autoload.php new file mode 100644 index 000000000..40ddbde74 --- /dev/null +++ b/include/ezcomponents/autoload/mail_autoload.php @@ -0,0 +1,81 @@ + 'Mail/exceptions/mail_exception.php', + 'ezcMailInvalidLimitException' => 'Mail/exceptions/invalid_limit.php', + 'ezcMailNoSuchMessageException' => 'Mail/exceptions/no_such_message.php', + 'ezcMailOffsetOutOfRangeException' => 'Mail/exceptions/offset_out_of_range.php', + 'ezcMailTransportException' => 'Mail/exceptions/transport_exception.php', + 'ezcMailTransportSmtpException' => 'Mail/exceptions/transport_smtp_exception.php', + 'ezcMailPart' => 'Mail/interfaces/part.php', + 'ezcMailPartParser' => 'Mail/parser/interfaces/part_parser.php', + 'ezcMailTransport' => 'Mail/interfaces/transport.php', + 'ezcMail' => 'Mail/mail.php', + 'ezcMailFilePart' => 'Mail/parts/file.php', + 'ezcMailMtaTransport' => 'Mail/transports/mta/mta_transport.php', + 'ezcMailMultipart' => 'Mail/parts/multipart.php', + 'ezcMailMultipartParser' => 'Mail/parser/parts/multipart_parser.php', + 'ezcMailParserSet' => 'Mail/parser/interfaces/parser_set.php', + 'ezcMailSmtpTransport' => 'Mail/transports/smtp/smtp_transport.php', + 'ezcMailTransportOptions' => 'Mail/options/transport_options.php', + 'ezcMailAddress' => 'Mail/structs/mail_address.php', + 'ezcMailCharsetConverter' => 'Mail/internal/charset_convert.php', + 'ezcMailComposer' => 'Mail/composer.php', + 'ezcMailComposerOptions' => 'Mail/options/composer_options.php', + 'ezcMailContentDispositionHeader' => 'Mail/structs/content_disposition_header.php', + 'ezcMailDeliveryStatus' => 'Mail/parts/delivery_status.php', + 'ezcMailDeliveryStatusParser' => 'Mail/parser/parts/delivery_status_parser.php', + 'ezcMailFile' => 'Mail/parts/fileparts/disk_file.php', + 'ezcMailFileParser' => 'Mail/parser/parts/file_parser.php', + 'ezcMailFileSet' => 'Mail/transports/file/file_set.php', + 'ezcMailHeaderFolder' => 'Mail/internal/header_folder.php', + 'ezcMailHeadersHolder' => 'Mail/parser/headers_holder.php', + 'ezcMailImapSet' => 'Mail/transports/imap/imap_set.php', + 'ezcMailImapSetOptions' => 'Mail/options/imap_set_options.php', + 'ezcMailImapTransport' => 'Mail/transports/imap/imap_transport.php', + 'ezcMailImapTransportOptions' => 'Mail/options/imap_options.php', + 'ezcMailMboxSet' => 'Mail/transports/mbox/mbox_set.php', + 'ezcMailMboxTransport' => 'Mail/transports/mbox/mbox_transport.php', + 'ezcMailMultipartAlternative' => 'Mail/parts/multiparts/multipart_alternative.php', + 'ezcMailMultipartAlternativeParser' => 'Mail/parser/parts/multipart_alternative_parser.php', + 'ezcMailMultipartDigest' => 'Mail/parts/multiparts/multipart_digest.php', + 'ezcMailMultipartDigestParser' => 'Mail/parser/parts/multipart_digest_parser.php', + 'ezcMailMultipartMixed' => 'Mail/parts/multiparts/multipart_mixed.php', + 'ezcMailMultipartMixedParser' => 'Mail/parser/parts/multipart_mixed_parser.php', + 'ezcMailMultipartRelated' => 'Mail/parts/multiparts/multipart_related.php', + 'ezcMailMultipartRelatedParser' => 'Mail/parser/parts/multipart_related_parser.php', + 'ezcMailMultipartReport' => 'Mail/parts/multiparts/multipart_report.php', + 'ezcMailMultipartReportParser' => 'Mail/parser/parts/multipart_report_parser.php', + 'ezcMailParser' => 'Mail/parser/parser.php', + 'ezcMailParserOptions' => 'Mail/options/parser_options.php', + 'ezcMailParserShutdownHandler' => 'Mail/parser/shutdown_handler.php', + 'ezcMailPartWalkContext' => 'Mail/structs/walk_context.php', + 'ezcMailPop3Set' => 'Mail/transports/pop3/pop3_set.php', + 'ezcMailPop3Transport' => 'Mail/transports/pop3/pop3_transport.php', + 'ezcMailPop3TransportOptions' => 'Mail/options/pop3_options.php', + 'ezcMailRfc2231Implementation' => 'Mail/parser/rfc2231_implementation.php', + 'ezcMailRfc822Digest' => 'Mail/parts/rfc822_digest.php', + 'ezcMailRfc822DigestParser' => 'Mail/parser/parts/rfc822_digest_parser.php', + 'ezcMailRfc822Parser' => 'Mail/parser/parts/rfc822_parser.php', + 'ezcMailSmtpTransportOptions' => 'Mail/options/smtp_options.php', + 'ezcMailStorageSet' => 'Mail/transports/storage/storage_set.php', + 'ezcMailStreamFile' => 'Mail/parts/fileparts/stream_file.php', + 'ezcMailText' => 'Mail/parts/text.php', + 'ezcMailTextParser' => 'Mail/parser/parts/text_parser.php', + 'ezcMailTools' => 'Mail/tools.php', + 'ezcMailTransportConnection' => 'Mail/transports/transport_connection.php', + 'ezcMailTransportMta' => 'Mail/transports/mta/transport_mta.php', + 'ezcMailTransportSmtp' => 'Mail/transports/smtp/transport_smtp.php', + 'ezcMailVariableSet' => 'Mail/transports/variable/var_set.php', + 'ezcMailVirtualFile' => 'Mail/parts/fileparts/virtual_file.php', +); +?> diff --git a/include/ezcomponents/autoload/persistent_autoload.php b/include/ezcomponents/autoload/persistent_autoload.php new file mode 100644 index 000000000..0ebc979bb --- /dev/null +++ b/include/ezcomponents/autoload/persistent_autoload.php @@ -0,0 +1,62 @@ + 'PersistentObject/exceptions/persistent_object_exception.php', + 'ezcPersistentDefinitionMissingIdPropertyException' => 'PersistentObject/exceptions/definition_missing_id_property.php', + 'ezcPersistentDefinitionNotFoundException' => 'PersistentObject/exceptions/definition_not_found.php', + 'ezcPersistentIdentifierGenerationException' => 'PersistentObject/exceptions/identifier_generation.php', + 'ezcPersistentInvalidObjectStateException' => 'PersistentObject/exceptions/invalid_object_state.php', + 'ezcPersistentObjectAlreadyPersistentException' => 'PersistentObject/exceptions/already_persistent.php', + 'ezcPersistentObjectNotPersistentException' => 'PersistentObject/exceptions/not_persistent.php', + 'ezcPersistentQueryException' => 'PersistentObject/exceptions/query_exception.php', + 'ezcPersistentRelatedObjectNotFoundException' => 'PersistentObject/exceptions/related_object_not_found.php', + 'ezcPersistentRelationInvalidException' => 'PersistentObject/exceptions/relation_invalid.php', + 'ezcPersistentRelationNotFoundException' => 'PersistentObject/exceptions/relation_not_found.php', + 'ezcPersistentRelationOperationNotSupportedException' => 'PersistentObject/exceptions/relation_operation_not_supported.php', + 'ezcPersistentSessionNotFoundException' => 'PersistentObject/exceptions/session_not_found.php', + 'ezcPersistentUndeterministicRelationException' => 'PersistentObject/exceptions/undeterministic_relation.php', + 'ezcPersistentDefinitionManager' => 'PersistentObject/interfaces/definition_manager.php', + 'ezcPersistentIdentifierGenerator' => 'PersistentObject/interfaces/identifier_generator.php', + 'ezcPersistentPropertyConverter' => 'PersistentObject/interfaces/property_converter.php', + 'ezcPersistentRelation' => 'PersistentObject/interfaces/relation.php', + 'ezcPersistentSessionHandler' => 'PersistentObject/interfaces/handler.php', + 'ezcPersistentCacheManager' => 'PersistentObject/managers/cache_manager.php', + 'ezcPersistentCodeManager' => 'PersistentObject/managers/code_manager.php', + 'ezcPersistentDeleteHandler' => 'PersistentObject/handlers/delete_handler.php', + 'ezcPersistentDoubleTableMap' => 'PersistentObject/structs/double_table_map.php', + 'ezcPersistentFindIterator' => 'PersistentObject/find_iterator.php', + 'ezcPersistentGeneratorDefinition' => 'PersistentObject/structs/generator_definition.php', + 'ezcPersistentLoadHandler' => 'PersistentObject/handlers/load_handler.php', + 'ezcPersistentManualGenerator' => 'PersistentObject/generators/manual_generator.php', + 'ezcPersistentManyToManyRelation' => 'PersistentObject/relations/many_to_many.php', + 'ezcPersistentManyToOneRelation' => 'PersistentObject/relations/many_to_one.php', + 'ezcPersistentMultiManager' => 'PersistentObject/managers/multi_manager.php', + 'ezcPersistentNativeGenerator' => 'PersistentObject/generators/native_generator.php', + 'ezcPersistentObject' => 'PersistentObject/interfaces/persistent_object.php', + 'ezcPersistentObjectColumns' => 'PersistentObject/object/persistent_object_columns.php', + 'ezcPersistentObjectDefinition' => 'PersistentObject/object/persistent_object_definition.php', + 'ezcPersistentObjectIdProperty' => 'PersistentObject/object/persistent_object_id_property.php', + 'ezcPersistentObjectProperties' => 'PersistentObject/object/persistent_object_properties.php', + 'ezcPersistentObjectProperty' => 'PersistentObject/object/persistent_object_property.php', + 'ezcPersistentObjectRelations' => 'PersistentObject/object/persistent_object_relations.php', + 'ezcPersistentOneToManyRelation' => 'PersistentObject/relations/one_to_many.php', + 'ezcPersistentOneToOneRelation' => 'PersistentObject/relations/one_to_one.php', + 'ezcPersistentPropertyDateTimeConverter' => 'PersistentObject/object/property_converters/date.php', + 'ezcPersistentRelationCollection' => 'PersistentObject/object/relation_collection.php', + 'ezcPersistentSaveHandler' => 'PersistentObject/handlers/save_handler.php', + 'ezcPersistentSequenceGenerator' => 'PersistentObject/generators/sequence_generator.php', + 'ezcPersistentSession' => 'PersistentObject/persistent_session.php', + 'ezcPersistentSessionInstance' => 'PersistentObject/persistent_session_instance.php', + 'ezcPersistentSingleTableMap' => 'PersistentObject/structs/single_table_map.php', + 'ezcPersistentStateTransformer' => 'PersistentObject/internal/state_transformer.php', +); +?> diff --git a/include/ezcomponents/autoload/persistent_object_autoload.php b/include/ezcomponents/autoload/persistent_object_autoload.php new file mode 100644 index 000000000..12b30c1e7 --- /dev/null +++ b/include/ezcomponents/autoload/persistent_object_autoload.php @@ -0,0 +1,15 @@ + 'PersistentObjectDatabaseSchemaTiein/generator.php', +); +?> diff --git a/include/ezcomponents/autoload/php_generator_autoload.php b/include/ezcomponents/autoload/php_generator_autoload.php new file mode 100644 index 000000000..c8b208949 --- /dev/null +++ b/include/ezcomponents/autoload/php_generator_autoload.php @@ -0,0 +1,19 @@ + 'PhpGenerator/exceptions/php_generator_exception.php', + 'ezcPhpGeneratorFlowException' => 'PhpGenerator/exceptions/flow_exception.php', + 'ezcPhpGenerator' => 'PhpGenerator/php_generator.php', + 'ezcPhpGeneratorParameter' => 'PhpGenerator/structs/php_generator_parameter.php', + 'ezcPhpGeneratorReturnData' => 'PhpGenerator/structs/php_generator_return_data.php', +); +?> diff --git a/include/ezcomponents/autoload/query_autoload.php b/include/ezcomponents/autoload/query_autoload.php new file mode 100644 index 000000000..90c4bc9fb --- /dev/null +++ b/include/ezcomponents/autoload/query_autoload.php @@ -0,0 +1,33 @@ + 'Database/exceptions/query_exception.php', + 'ezcQueryInvalidException' => 'Database/exceptions/query/invalid.php', + 'ezcQueryInvalidParameterException' => 'Database/exceptions/query/invalid_parameter.php', + 'ezcQueryVariableParameterException' => 'Database/exceptions/query/variable_parameter.php', + 'ezcQuery' => 'Database/sqlabstraction/query.php', + 'ezcQueryExpression' => 'Database/sqlabstraction/expression.php', + 'ezcQuerySelect' => 'Database/sqlabstraction/query_select.php', + 'ezcQueryDelete' => 'Database/sqlabstraction/query_delete.php', + 'ezcQueryExpressionMssql' => 'Database/sqlabstraction/implementations/expression_mssql.php', + 'ezcQueryExpressionOracle' => 'Database/sqlabstraction/implementations/expression_oracle.php', + 'ezcQueryExpressionPgsql' => 'Database/sqlabstraction/implementations/expression_pgsql.php', + 'ezcQueryExpressionSqlite' => 'Database/sqlabstraction/implementations/expression_sqlite.php', + 'ezcQueryInsert' => 'Database/sqlabstraction/query_insert.php', + 'ezcQuerySelectMssql' => 'Database/sqlabstraction/implementations/query_select_mssql.php', + 'ezcQuerySelectOracle' => 'Database/sqlabstraction/implementations/query_select_oracle.php', + 'ezcQuerySelectSqlite' => 'Database/sqlabstraction/implementations/query_select_sqlite.php', + 'ezcQuerySqliteFunctions' => 'Database/sqlabstraction/implementations/query_sqlite_function_implementations.php', + 'ezcQuerySubSelect' => 'Database/sqlabstraction/query_subselect.php', + 'ezcQueryUpdate' => 'Database/sqlabstraction/query_update.php', +); +?> diff --git a/include/ezcomponents/autoload/search_autoload.php b/include/ezcomponents/autoload/search_autoload.php new file mode 100644 index 000000000..ebf3d9e59 --- /dev/null +++ b/include/ezcomponents/autoload/search_autoload.php @@ -0,0 +1,46 @@ + 'Search/exceptions/exception.php', + 'ezcSearchBuildQueryException' => 'Search/exceptions/build_query.php', + 'ezcSearchCanNotConnectException' => 'Search/exceptions/can_not_connect.php', + 'ezcSearchDefinitionInvalidException' => 'Search/exceptions/definition_invalid.php', + 'ezcSearchDefinitionNotFoundException' => 'Search/exceptions/definition_not_found.php', + 'ezcSearchDoesNotProvideDefinitionException' => 'Search/exceptions/does_not_provide_definition.php', + 'ezcSearchFieldNotDefinedException' => 'Search/exceptions/field_not_defined.php', + 'ezcSearchIncompleteStateException' => 'Search/exceptions/incomplete_state.php', + 'ezcSearchQueryVariableParameterException' => 'Search/exceptions/query_variable_parameter.php', + 'ezcSearchTransactionException' => 'Search/exceptions/transaction.php', + 'ezcSearchQuery' => 'Search/interfaces/query.php', + 'ezcSearchDefinitionManager' => 'Search/interfaces/definition_manager.php', + 'ezcSearchDefinitionProvider' => 'Search/interfaces/definition_provider.php', + 'ezcSearchFindQuery' => 'Search/interfaces/query_find.php', + 'ezcSearchHandler' => 'Search/interfaces/handler.php', + 'ezcSearchIndexHandler' => 'Search/interfaces/index_handler.php', + 'ezcSearchDefinitionDocumentField' => 'Search/structs/document_field_definition.php', + 'ezcSearchDeleteQuery' => 'Search/interfaces/query_delete.php', + 'ezcSearchDocumentDefinition' => 'Search/document_definition.php', + 'ezcSearchEmbeddedManager' => 'Search/managers/embedded_manager.php', + 'ezcSearchQueryBuilder' => 'Search/query_builder.php', + 'ezcSearchQuerySolr' => 'Search/abstraction/implementations/solr.php', + 'ezcSearchQueryToken' => 'Search/structs/query_token.php', + 'ezcSearchQueryTools' => 'Search/abstraction/query_tools.php', + 'ezcSearchResult' => 'Search/structs/search_result.php', + 'ezcSearchResultDocument' => 'Search/structs/search_result_document.php', + 'ezcSearchRstXmlExtractor' => 'Search/extractors/rstxml.php', + 'ezcSearchSession' => 'Search/search_session.php', + 'ezcSearchSimpleArticle' => 'Search/extractors/helpers/simple.php', + 'ezcSearchSimpleImage' => 'Search/extractors/helpers/image.php', + 'ezcSearchSolrHandler' => 'Search/handlers/solr.php', + 'ezcSearchXmlManager' => 'Search/managers/xml_manager.php', +); +?> diff --git a/include/ezcomponents/autoload/signal_autoload.php b/include/ezcomponents/autoload/signal_autoload.php new file mode 100644 index 000000000..17db92de4 --- /dev/null +++ b/include/ezcomponents/autoload/signal_autoload.php @@ -0,0 +1,20 @@ + 'SignalSlot/exceptions/signalslot_exception.php', + 'ezcSignalStaticConnectionsBase' => 'SignalSlot/interfaces/static_connections_base.php', + 'ezcSignalCallbackComparer' => 'SignalSlot/internal/callback_comparer.php', + 'ezcSignalCollection' => 'SignalSlot/signal_collection.php', + 'ezcSignalCollectionOptions' => 'SignalSlot/options/options.php', + 'ezcSignalStaticConnections' => 'SignalSlot/static_connections.php', +); +?> diff --git a/include/ezcomponents/autoload/system_autoload.php b/include/ezcomponents/autoload/system_autoload.php new file mode 100644 index 000000000..454938127 --- /dev/null +++ b/include/ezcomponents/autoload/system_autoload.php @@ -0,0 +1,22 @@ + 'SystemInformation/system/exceptions/cant_scan.php', + 'ezcSystemInfoReader' => 'SystemInformation/system/interfaces/info_reader.php', + 'ezcSystemInfo' => 'SystemInformation/system/info.php', + 'ezcSystemInfoAccelerator' => 'SystemInformation/system/structs/accelerator_info.php', + 'ezcSystemInfoFreeBsdReader' => 'SystemInformation/system/readers/info_freebsd.php', + 'ezcSystemInfoLinuxReader' => 'SystemInformation/system/readers/info_linux.php', + 'ezcSystemInfoMacReader' => 'SystemInformation/system/readers/info_mac.php', + 'ezcSystemInfoWindowsReader' => 'SystemInformation/system/readers/info_windows.php', +); +?> diff --git a/include/ezcomponents/autoload/template_autoload.php b/include/ezcomponents/autoload/template_autoload.php new file mode 100644 index 000000000..d07e80d5f --- /dev/null +++ b/include/ezcomponents/autoload/template_autoload.php @@ -0,0 +1,299 @@ + 'Template/exceptions/template_exception.php', + 'ezcTemplateCompilationFailedException' => 'Template/exceptions/compilation_failed_exception.php', + 'ezcTemplateCustomBlockException' => 'Template/exceptions/custom_block_exception.php', + 'ezcTemplateFileFailedRenameException' => 'Template/exceptions/file_failed_rename_exception.php', + 'ezcTemplateFileFailedUnlinkException' => 'Template/exceptions/file_failed_unlink_exception.php', + 'ezcTemplateFileNotFoundException' => 'Template/exceptions/file_not_found_exception.php', + 'ezcTemplateFileNotReadableException' => 'Template/exceptions/file_not_readable_exception.php', + 'ezcTemplateFileNotWriteableException' => 'Template/exceptions/file_not_writeable_exception.php', + 'ezcTemplateInternalException' => 'Template/exceptions/internal_exception.php', + 'ezcTemplateInvalidCompiledFileException' => 'Template/exceptions/invalid_compiled_file_exception.php', + 'ezcTemplateNoManagerException' => 'Template/exceptions/no_manager_exception.php', + 'ezcTemplateNoOutputContextException' => 'Template/exceptions/no_output_context_exception.php', + 'ezcTemplateOutdatedCompilationException' => 'Template/exceptions/outdated_compilation_exception.php', + 'ezcTemplateParserException' => 'Template/exceptions/parser_exception.php', + 'ezcTemplateRuntimeException' => 'Template/exceptions/runtime_exception.php', + 'ezcTemplateSourceToTstParserException' => 'Template/exceptions/source_to_tst_parser_exception.php', + 'ezcTemplateTstNodeException' => 'Template/exceptions/tst_node_exception.php', + 'ezcTemplateTypeHintException' => 'Template/exceptions/typehint_exception.php', + 'ezcTemplateAstNode' => 'Template/syntax_trees/ast/interfaces/ast_node.php', + 'ezcTemplateParameterizedAstNode' => 'Template/syntax_trees/ast/interfaces/parameterized_ast.php', + 'ezcTemplateTstNode' => 'Template/syntax_trees/tst/interfaces/tst_node.php', + 'ezcTemplateExpressionTstNode' => 'Template/syntax_trees/tst/interfaces/expression_tst.php', + 'ezcTemplateOperatorAstNode' => 'Template/syntax_trees/ast/interfaces/operator_ast.php', + 'ezcTemplateAstNodeVisitor' => 'Template/syntax_trees/ast/interfaces/ast_visitor.php', + 'ezcTemplateBinaryOperatorAstNode' => 'Template/syntax_trees/ast/interfaces/binary_operator.php', + 'ezcTemplateCodeTstNode' => 'Template/syntax_trees/tst/interfaces/code_tst.php', + 'ezcTemplateOperatorTstNode' => 'Template/syntax_trees/tst/interfaces/operator_tst.php', + 'ezcTemplateSourceToTstParser' => 'Template/parsers/source_to_tst/interfaces/source_to_tst_parser.php', + 'ezcTemplateStatementAstNode' => 'Template/syntax_trees/ast/interfaces/statement_ast.php', + 'ezcTemplateTstNodeVisitor' => 'Template/syntax_trees/tst/interfaces/tst_visitor.php', + 'ezcTemplateAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/assignment_operator.php', + 'ezcTemplateAstToPhpGenerator' => 'Template/parsers/ast_to_php/implementations/php_generator.php', + 'ezcTemplateAstWalker' => 'Template/parsers/ast_to_ast/implementations/ast_walker.php', + 'ezcTemplateBlockTstNode' => 'Template/syntax_trees/tst/nodes/block.php', + 'ezcTemplateBodyAstNode' => 'Template/syntax_trees/ast/nodes/body.php', + 'ezcTemplateCaseAstNode' => 'Template/syntax_trees/ast/nodes/control/case.php', + 'ezcTemplateCustomExtension' => 'Template/structs/custom_extension.php', + 'ezcTemplateFunctions' => 'Template/functions/functions.php', + 'ezcTemplateLiteralSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/literal.php', + 'ezcTemplateLocation' => 'Template/interfaces/location.php', + 'ezcTemplateModifyingOperatorTstNode' => 'Template/syntax_trees/tst/interfaces/modifying_operator_tst.php', + 'ezcTemplateOutputContext' => 'Template/interfaces/output_context.php', + 'ezcTemplateTextTstNode' => 'Template/syntax_trees/tst/interfaces/text_tst.php', + 'ezcTemplateTreeOutput' => 'Template/parsers/interfaces/tree_output.php', + 'ezcTemplateTstToAstTransformer' => 'Template/parsers/tst_to_ast/implementations/tst_to_ast_transformer.php', + 'ezcTemplateTstWalker' => 'Template/parsers/tst_to_tst/implementations/tst_walker.php', + 'ezcTemplate' => 'Template/template.php', + 'ezcTemplateAdditionAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/addition_assignment_operator.php', + 'ezcTemplateAdditionOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/addition_operator.php', + 'ezcTemplateArithmeticNegationOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/arithmetic_negation_operator.php', + 'ezcTemplateArray' => 'Template/functions/array_code.php', + 'ezcTemplateArrayAppendOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/array_append_assignment_operator.php', + 'ezcTemplateArrayAppendOperatorTstNode' => 'Template/syntax_trees/tst/nodes/array_append_operator.php', + 'ezcTemplateArrayFetchOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/array_fetch_operator.php', + 'ezcTemplateArrayFetchOperatorTstNode' => 'Template/syntax_trees/tst/nodes/array_fetch_operator.php', + 'ezcTemplateArrayFetchSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/array_fetch.php', + 'ezcTemplateArrayFunctions' => 'Template/functions/array_functions.php', + 'ezcTemplateArrayRangeOperatorTstNode' => 'Template/syntax_trees/tst/nodes/array_range_operator.php', + 'ezcTemplateArraySourceToTstParser' => 'Template/parsers/source_to_tst/implementations/array.php', + 'ezcTemplateAssignmentOperatorTstNode' => 'Template/syntax_trees/tst/nodes/assignment_operator.php', + 'ezcTemplateAstBuilder' => 'Template/syntax_trees/ast/ast_builder.php', + 'ezcTemplateAstToAstAssignmentOptimizer' => 'Template/parsers/ast_to_ast/implementations/assignment_optimizer.php', + 'ezcTemplateAstToAstContextAppender' => 'Template/parsers/ast_to_ast/implementations/context_appender.php', + 'ezcTemplateAstToPhpStringGenerator' => 'Template/parsers/ast_to_php/implementations/php_string_generator.php', + 'ezcTemplateAstTreeOutput' => 'Template/parsers/ast/implementations/ast_tree_output.php', + 'ezcTemplateAutoloaderDefinition' => 'Template/structs/autoloader_definition.php', + 'ezcTemplateBitwiseAndAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/bitwise_and_assignment_operator.php', + 'ezcTemplateBitwiseAndOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/bitwise_and_operator.php', + 'ezcTemplateBitwiseNegationOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/bitwise_negation_operator.php', + 'ezcTemplateBitwiseOrAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/bitwise_or_assignment_operator.php', + 'ezcTemplateBitwiseOrOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/bitwise_or_operator.php', + 'ezcTemplateBitwiseXorAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/bitwise_xor_assignment_operator.php', + 'ezcTemplateBitwiseXorOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/bitwise_xor_operator.php', + 'ezcTemplateBlockCommentAstNode' => 'Template/syntax_trees/ast/nodes/block_comment.php', + 'ezcTemplateBlockCommentSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/block_comment.php', + 'ezcTemplateBlockCommentTstNode' => 'Template/syntax_trees/tst/nodes/block_comment.php', + 'ezcTemplateBlockSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/block.php', + 'ezcTemplateBoolSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/bool.php', + 'ezcTemplateBreakAstNode' => 'Template/syntax_trees/ast/nodes/control/break.php', + 'ezcTemplateCacheBlockAstNode' => 'Template/syntax_trees/ast/nodes/cache_block.php', + 'ezcTemplateCacheBlockTstNode' => 'Template/syntax_trees/tst/nodes/cache_block.php', + 'ezcTemplateCacheManager' => 'Template/interfaces/cache_manager.php', + 'ezcTemplateCacheSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/cache.php', + 'ezcTemplateCacheTstNode' => 'Template/syntax_trees/tst/nodes/cache.php', + 'ezcTemplateCaptureSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/capture.php', + 'ezcTemplateCaptureTstNode' => 'Template/syntax_trees/tst/nodes/capture.php', + 'ezcTemplateCaseTstNode' => 'Template/syntax_trees/tst/nodes/case.php', + 'ezcTemplateCatchAstNode' => 'Template/syntax_trees/ast/nodes/control/catch.php', + 'ezcTemplateCharsetSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/charset.php', + 'ezcTemplateCharsetTstNode' => 'Template/syntax_trees/tst/nodes/charset.php', + 'ezcTemplateCloneAstNode' => 'Template/syntax_trees/ast/nodes/constructs/clone.php', + 'ezcTemplateCompiledCode' => 'Template/compiled_code.php', + 'ezcTemplateConcatAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/concat_assignment_operator.php', + 'ezcTemplateConcatAssignmentOperatorTstNode' => 'Template/syntax_trees/tst/nodes/concat_assignment_operator.php', + 'ezcTemplateConcatOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/concat_operator.php', + 'ezcTemplateConcatOperatorTstNode' => 'Template/syntax_trees/tst/nodes/concat_operator.php', + 'ezcTemplateConditionBodyAstNode' => 'Template/syntax_trees/ast/nodes/condition_body.php', + 'ezcTemplateConditionBodyTstNode' => 'Template/syntax_trees/tst/nodes/condition_body.php', + 'ezcTemplateConfiguration' => 'Template/configuration.php', + 'ezcTemplateConstantAstNode' => 'Template/syntax_trees/ast/nodes/constant.php', + 'ezcTemplateContinueAstNode' => 'Template/syntax_trees/ast/nodes/control/continue.php', + 'ezcTemplateControlStructureSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/control_structure.php', + 'ezcTemplateCurlyBracesAstNode' => 'Template/syntax_trees/ast/nodes/curly_braces.php', + 'ezcTemplateCursor' => 'Template/cursor.php', + 'ezcTemplateCustomBlock' => 'Template/interfaces/custom_block.php', + 'ezcTemplateCustomBlockDefinition' => 'Template/structs/custom_block_definition.php', + 'ezcTemplateCustomBlockSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/custom_block.php', + 'ezcTemplateCustomBlockTstNode' => 'Template/syntax_trees/tst/nodes/custom_block.php', + 'ezcTemplateCustomFunction' => 'Template/interfaces/custom_function.php', + 'ezcTemplateCustomFunctionDefinition' => 'Template/structs/custom_function_definition.php', + 'ezcTemplateCycle' => 'Template/cycle.php', + 'ezcTemplateCycleControlTstNode' => 'Template/syntax_trees/tst/nodes/cycle_control.php', + 'ezcTemplateCycleSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/cycle.php', + 'ezcTemplateDate' => 'Template/functions/date_code.php', + 'ezcTemplateDateFunctions' => 'Template/functions/date_functions.php', + 'ezcTemplateDebug' => 'Template/functions/debug_code.php', + 'ezcTemplateDebugFunctions' => 'Template/functions/debug_functions.php', + 'ezcTemplateDeclarationBlockSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/declaration.php', + 'ezcTemplateDeclarationTstNode' => 'Template/syntax_trees/tst/nodes/declaration.php', + 'ezcTemplateDecrementOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/decrement_operator.php', + 'ezcTemplateDefaultAstNode' => 'Template/syntax_trees/ast/nodes/control/default.php', + 'ezcTemplateDelimiterSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/delimiter.php', + 'ezcTemplateDelimiterTstNode' => 'Template/syntax_trees/tst/nodes/delimiter.php', + 'ezcTemplateDivisionAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/division_assignment_operator.php', + 'ezcTemplateDivisionAssignmentOperatorTstNode' => 'Template/syntax_trees/tst/nodes/division_assignment_operator.php', + 'ezcTemplateDivisionOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/division_operator.php', + 'ezcTemplateDivisionOperatorTstNode' => 'Template/syntax_trees/tst/nodes/division_operator.php', + 'ezcTemplateDoWhileAstNode' => 'Template/syntax_trees/ast/nodes/control/do_while.php', + 'ezcTemplateDocCommentSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/doc_comment.php', + 'ezcTemplateDocCommentTstNode' => 'Template/syntax_trees/tst/nodes/doc_comment.php', + 'ezcTemplateDynamicBlockAstNode' => 'Template/syntax_trees/ast/nodes/dynamic_block.php', + 'ezcTemplateDynamicBlockTstNode' => 'Template/syntax_trees/tst/nodes/dynamic_block.php', + 'ezcTemplateDynamicStringAstNode' => 'Template/syntax_trees/ast/nodes/dynamic_string.php', + 'ezcTemplateDynamicVariableAstNode' => 'Template/syntax_trees/ast/nodes/dynamic_variable.php', + 'ezcTemplateEchoAstNode' => 'Template/syntax_trees/ast/nodes/constructs/echo.php', + 'ezcTemplateEmptyAstNode' => 'Template/syntax_trees/ast/nodes/constructs/empty.php', + 'ezcTemplateEmptyBlockTstNode' => 'Template/syntax_trees/tst/nodes/empty_block.php', + 'ezcTemplateEolCommentAstNode' => 'Template/syntax_trees/ast/nodes/eol_comment.php', + 'ezcTemplateEolCommentSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/eol_comment.php', + 'ezcTemplateEolCommentTstNode' => 'Template/syntax_trees/tst/nodes/eol_comment.php', + 'ezcTemplateEqualOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/equal_operator.php', + 'ezcTemplateEqualOperatorTstNode' => 'Template/syntax_trees/tst/nodes/equal_operator.php', + 'ezcTemplateExpressionBlockSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/expression_block.php', + 'ezcTemplateExpressionSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/expression.php', + 'ezcTemplateFetchCacheInformation' => 'Template/parsers/tst_to_tst/implementations/cache_information.php', + 'ezcTemplateFloatSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/float.php', + 'ezcTemplateForAstNode' => 'Template/syntax_trees/ast/nodes/control/for.php', + 'ezcTemplateForeachAstNode' => 'Template/syntax_trees/ast/nodes/control/foreach.php', + 'ezcTemplateForeachLoopSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/foreach_loop.php', + 'ezcTemplateForeachLoopTstNode' => 'Template/syntax_trees/tst/nodes/foreach_loop.php', + 'ezcTemplateFunctionCallAstNode' => 'Template/syntax_trees/ast/nodes/function_call.php', + 'ezcTemplateFunctionCallSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/function_call.php', + 'ezcTemplateFunctionCallTstNode' => 'Template/syntax_trees/tst/nodes/function_call.php', + 'ezcTemplateGenericStatementAstNode' => 'Template/syntax_trees/ast/nodes/generic_statement.php', + 'ezcTemplateGreaterEqualOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/greater_equal_operator.php', + 'ezcTemplateGreaterEqualOperatorTstNode' => 'Template/syntax_trees/tst/nodes/greater_equal_operator.php', + 'ezcTemplateGreaterThanOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/greater_than_operator.php', + 'ezcTemplateGreaterThanOperatorTstNode' => 'Template/syntax_trees/tst/nodes/greater_than_operator.php', + 'ezcTemplateIdenticalOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/identical_operator.php', + 'ezcTemplateIdenticalOperatorTstNode' => 'Template/syntax_trees/tst/nodes/identical_operator.php', + 'ezcTemplateIdentifierAstNode' => 'Template/syntax_trees/ast/nodes/identifier.php', + 'ezcTemplateIdentifierSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/identifier.php', + 'ezcTemplateIdentifierTstNode' => 'Template/syntax_trees/tst/nodes/identifier.php', + 'ezcTemplateIfAstNode' => 'Template/syntax_trees/ast/nodes/control/if.php', + 'ezcTemplateIfConditionSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/if_condition.php', + 'ezcTemplateIfConditionTstNode' => 'Template/syntax_trees/tst/nodes/if_condition.php', + 'ezcTemplateIncludeAstNode' => 'Template/syntax_trees/ast/nodes/control/include.php', + 'ezcTemplateIncludeOnceAstNode' => 'Template/syntax_trees/ast/nodes/control/include_once.php', + 'ezcTemplateIncludeSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/include.php', + 'ezcTemplateIncludeTstNode' => 'Template/syntax_trees/tst/nodes/include.php', + 'ezcTemplateIncrementOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/increment_operator.php', + 'ezcTemplateInstanceofOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/instanceof_operator.php', + 'ezcTemplateIntegerSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/integer.php', + 'ezcTemplateIssetAstNode' => 'Template/syntax_trees/ast/nodes/constructs/isset.php', + 'ezcTemplateLessEqualOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/less_equal_operator.php', + 'ezcTemplateLessEqualOperatorTstNode' => 'Template/syntax_trees/tst/nodes/less_equal_operator.php', + 'ezcTemplateLessThanOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/less_than_operator.php', + 'ezcTemplateLessThanOperatorTstNode' => 'Template/syntax_trees/tst/nodes/less_than_operator.php', + 'ezcTemplateLiteralArrayAstNode' => 'Template/syntax_trees/ast/nodes/literalarray.php', + 'ezcTemplateLiteralArrayTstNode' => 'Template/syntax_trees/tst/nodes/literalarray.php', + 'ezcTemplateLiteralAstNode' => 'Template/syntax_trees/ast/nodes/literal.php', + 'ezcTemplateLiteralBlockSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/literal_block.php', + 'ezcTemplateLiteralBlockTstNode' => 'Template/syntax_trees/tst/nodes/literal_block.php', + 'ezcTemplateLiteralTstNode' => 'Template/syntax_trees/tst/nodes/literal.php', + 'ezcTemplateLocationInterface' => 'Template/interfaces/location_interface.php', + 'ezcTemplateLocator' => 'Template/interfaces/locator.php', + 'ezcTemplateLogicalAndOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/logical_and_operator.php', + 'ezcTemplateLogicalAndOperatorTstNode' => 'Template/syntax_trees/tst/nodes/logical_and_operator.php', + 'ezcTemplateLogicalLiteralAndOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/logical_literal_and_operator.php', + 'ezcTemplateLogicalLiteralOrOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/logical_literal_or_operator.php', + 'ezcTemplateLogicalLiteralXorOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/logical_literal_xor_operator.php', + 'ezcTemplateLogicalNegateOperatorTstNode' => 'Template/syntax_trees/tst/nodes/logical_negate_operator.php', + 'ezcTemplateLogicalNegationOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/logical_negation_operator.php', + 'ezcTemplateLogicalOrOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/logical_or_operator.php', + 'ezcTemplateLogicalOrOperatorTstNode' => 'Template/syntax_trees/tst/nodes/logical_or_operator.php', + 'ezcTemplateLoopSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/loop.php', + 'ezcTemplateLoopTstNode' => 'Template/syntax_trees/tst/nodes/loop.php', + 'ezcTemplateMathFunctions' => 'Template/functions/math_functions.php', + 'ezcTemplateMinusAssignmentOperatorTstNode' => 'Template/syntax_trees/tst/nodes/minus_assignment_operator.php', + 'ezcTemplateMinusOperatorTstNode' => 'Template/syntax_trees/tst/nodes/minus_operator.php', + 'ezcTemplateModifyingBlockTstNode' => 'Template/syntax_trees/tst/nodes/modifying_block.php', + 'ezcTemplateModuloAssignmentOperatorTstNode' => 'Template/syntax_trees/tst/nodes/modulo_assignment_operator.php', + 'ezcTemplateModuloOperatorTstNode' => 'Template/syntax_trees/tst/nodes/modulo_operator.php', + 'ezcTemplateModulusAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/modulus_assignment_operator.php', + 'ezcTemplateModulusOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/modulus_operator.php', + 'ezcTemplateMultiplicationAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/multiplication_assignment_operator.php', + 'ezcTemplateMultiplicationAssignmentOperatorTstNode' => 'Template/syntax_trees/tst/nodes/multiplication_assignment_operator.php', + 'ezcTemplateMultiplicationOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/multiplication_operator.php', + 'ezcTemplateMultiplicationOperatorTstNode' => 'Template/syntax_trees/tst/nodes/multiplication_operator.php', + 'ezcTemplateNegateOperatorTstNode' => 'Template/syntax_trees/tst/nodes/negate_operator.php', + 'ezcTemplateNewAstNode' => 'Template/syntax_trees/ast/nodes/constructs/new.php', + 'ezcTemplateNoContext' => 'Template/contexts/no_context.php', + 'ezcTemplateNopAstNode' => 'Template/syntax_trees/ast/nodes/nop.php', + 'ezcTemplateNotEqualOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/not_equal_operator.php', + 'ezcTemplateNotEqualOperatorTstNode' => 'Template/syntax_trees/tst/nodes/not_equal_operator.php', + 'ezcTemplateNotIdenticalOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/not_identical_operator.php', + 'ezcTemplateNotIdenticalOperatorTstNode' => 'Template/syntax_trees/tst/nodes/not_identical_operator.php', + 'ezcTemplateNullSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/null.php', + 'ezcTemplateObjectAccessOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/object_access_operator.php', + 'ezcTemplateOutputAstNode' => 'Template/syntax_trees/ast/nodes/output.php', + 'ezcTemplateOutputBlockTstNode' => 'Template/syntax_trees/tst/nodes/output_block.php', + 'ezcTemplateOutputVariableManager' => 'Template/parsers/tst_to_ast/implementations/output_variable_manager.php', + 'ezcTemplateParenthesisAstNode' => 'Template/syntax_trees/ast/nodes/parenthesis.php', + 'ezcTemplateParenthesisTstNode' => 'Template/syntax_trees/tst/nodes/parenthesis.php', + 'ezcTemplateParser' => 'Template/parser.php', + 'ezcTemplatePhpCodeAstNode' => 'Template/syntax_trees/ast/nodes/php_code.php', + 'ezcTemplatePlusAssignmentOperatorTstNode' => 'Template/syntax_trees/tst/nodes/plus_assignment_operator.php', + 'ezcTemplatePlusOperatorTstNode' => 'Template/syntax_trees/tst/nodes/plus_operator.php', + 'ezcTemplatePostDecrementOperatorTstNode' => 'Template/syntax_trees/tst/nodes/post_decrement_operator.php', + 'ezcTemplatePostIncrementOperatorTstNode' => 'Template/syntax_trees/tst/nodes/post_increment_operator.php', + 'ezcTemplatePreDecrementOperatorTstNode' => 'Template/syntax_trees/tst/nodes/pre_decrement_operator.php', + 'ezcTemplatePreIncrementOperatorTstNode' => 'Template/syntax_trees/tst/nodes/pre_increment_operator.php', + 'ezcTemplatePrintAstNode' => 'Template/syntax_trees/ast/nodes/constructs/print.php', + 'ezcTemplateProgramSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/program.php', + 'ezcTemplateProgramTstNode' => 'Template/syntax_trees/tst/nodes/program.php', + 'ezcTemplatePropertyFetchOperatorTstNode' => 'Template/syntax_trees/tst/nodes/property_fetch_operator.php', + 'ezcTemplateReferenceOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/reference_operator.php', + 'ezcTemplateRegExp' => 'Template/functions/regexp_code.php', + 'ezcTemplateRegExpFunctions' => 'Template/functions/regexp_functions.php', + 'ezcTemplateRequireAstNode' => 'Template/syntax_trees/ast/nodes/control/require.php', + 'ezcTemplateRequireOnceAstNode' => 'Template/syntax_trees/ast/nodes/control/require_once.php', + 'ezcTemplateReturnAstNode' => 'Template/syntax_trees/ast/nodes/control/return.php', + 'ezcTemplateReturnTstNode' => 'Template/syntax_trees/tst/nodes/return.php', + 'ezcTemplateRootAstNode' => 'Template/syntax_trees/ast/nodes/root.php', + 'ezcTemplateShiftLeftAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/shift_left_assignment_operator.php', + 'ezcTemplateShiftLeftOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/shift_left_operator.php', + 'ezcTemplateShiftRightAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/shift_right_assignment_operator.php', + 'ezcTemplateShiftRightOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/shift_right_operator.php', + 'ezcTemplateSourceCode' => 'Template/source_code.php', + 'ezcTemplateSourceToTstErrorMessages' => 'Template/error_messages.php', + 'ezcTemplateString' => 'Template/functions/string_code.php', + 'ezcTemplateStringFunctions' => 'Template/functions/string_functions.php', + 'ezcTemplateStringSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/string.php', + 'ezcTemplateStringTool' => 'Template/string_tool.php', + 'ezcTemplateSubtractionAssignmentOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/subtraction_assignment_operator.php', + 'ezcTemplateSubtractionOperatorAstNode' => 'Template/syntax_trees/ast/nodes/operators/subtraction_operator.php', + 'ezcTemplateSwitchAstNode' => 'Template/syntax_trees/ast/nodes/control/switch.php', + 'ezcTemplateSwitchConditionSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/switch_condition.php', + 'ezcTemplateSwitchTstNode' => 'Template/syntax_trees/tst/nodes/switch.php', + 'ezcTemplateSymbolTable' => 'Template/symbol_table.php', + 'ezcTemplateTextBlockTstNode' => 'Template/syntax_trees/tst/nodes/text_block.php', + 'ezcTemplateThrowExceptionAstNode' => 'Template/syntax_trees/ast/nodes/throw_exception.php', + 'ezcTemplateTranslationContextSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/translation_context.php', + 'ezcTemplateTranslationContextTstNode' => 'Template/syntax_trees/tst/nodes/translation_context.php', + 'ezcTemplateTranslationSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/translation.php', + 'ezcTemplateTranslationTstNode' => 'Template/syntax_trees/tst/nodes/translation.php', + 'ezcTemplateTryAstNode' => 'Template/syntax_trees/ast/nodes/control/try.php', + 'ezcTemplateTstToAstCachedTransformer' => 'Template/parsers/tst_to_ast/implementations/tst_to_ast_cached_transformer.php', + 'ezcTemplateTstTreeOutput' => 'Template/parsers/tst/implementations/tst_tree_output.php', + 'ezcTemplateType' => 'Template/functions/type_code.php', + 'ezcTemplateTypeCastAstNode' => 'Template/syntax_trees/ast/nodes/type_cast.php', + 'ezcTemplateTypeFunctions' => 'Template/functions/type_functions.php', + 'ezcTemplateUnsetAstNode' => 'Template/syntax_trees/ast/nodes/constructs/unset.php', + 'ezcTemplateValidationItem' => 'Template/validation_item.php', + 'ezcTemplateVariableAstNode' => 'Template/syntax_trees/ast/nodes/variable.php', + 'ezcTemplateVariableCollection' => 'Template/variable_collection.php', + 'ezcTemplateVariableSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/variable.php', + 'ezcTemplateVariableTstNode' => 'Template/syntax_trees/tst/nodes/variable.php', + 'ezcTemplateWeb' => 'Template/functions/web_code.php', + 'ezcTemplateWebFunctions' => 'Template/functions/web_functions.php', + 'ezcTemplateWhileAstNode' => 'Template/syntax_trees/ast/nodes/control/while.php', + 'ezcTemplateWhileLoopSourceToTstParser' => 'Template/parsers/source_to_tst/implementations/while_loop.php', + 'ezcTemplateWhileLoopTstNode' => 'Template/syntax_trees/tst/nodes/while_loop.php', + 'ezcTemplateWhitespaceRemoval' => 'Template/parsers/tst_to_tst/implementations/whitespace_removal.php', + 'ezcTemplateXhtmlContext' => 'Template/contexts/xhtml_context.php', +); +?> diff --git a/include/ezcomponents/autoload/template_translation_autoload.php b/include/ezcomponents/autoload/template_translation_autoload.php new file mode 100644 index 000000000..046b3650f --- /dev/null +++ b/include/ezcomponents/autoload/template_translation_autoload.php @@ -0,0 +1,19 @@ + 'TemplateTranslationTiein/exceptions/exception.php', + 'ezcTemplateTranslationManagerNotConfiguredException' => 'TemplateTranslationTiein/exceptions/manager_not_configured.php', + 'ezcTemplateTranslationConfiguration' => 'TemplateTranslationTiein/configuration.php', + 'ezcTemplateTranslationProvider' => 'TemplateTranslationTiein/provider.php', + 'ezcTemplateTranslationStringExtracter' => 'TemplateTranslationTiein/visitors/string_extracter.php', +); +?> diff --git a/include/ezcomponents/autoload/translation_autoload.php b/include/ezcomponents/autoload/translation_autoload.php new file mode 100644 index 000000000..4dfe434e6 --- /dev/null +++ b/include/ezcomponents/autoload/translation_autoload.php @@ -0,0 +1,34 @@ + 'Translation/exceptions/exception.php', + 'ezcTranslationContextNotAvailableException' => 'Translation/exceptions/context_not_available.php', + 'ezcTranslationKeyNotAvailableException' => 'Translation/exceptions/key_not_available.php', + 'ezcTranslationMissingTranslationFileException' => 'Translation/exceptions/missing_translation_file.php', + 'ezcTranslationNotConfiguredException' => 'Translation/exceptions/not_configured.php', + 'ezcTranslationParameterMissingException' => 'Translation/exceptions/parameter_missing.php', + 'ezcTranslationReaderNotInitializedException' => 'Translation/exceptions/reader_not_initialized.php', + 'ezcTranslationWriterNotInitializedException' => 'Translation/exceptions/writer_not_initialized.php', + 'ezcTranslationBackend' => 'Translation/interfaces/backend_interface.php', + 'ezcTranslationContextRead' => 'Translation/interfaces/context_read_interface.php', + 'ezcTranslationFilter' => 'Translation/interfaces/filter_interface.php', + 'ezcTranslation' => 'Translation/translation.php', + 'ezcTranslationBorkFilter' => 'Translation/filters/bork_filter.php', + 'ezcTranslationComplementEmptyFilter' => 'Translation/filters/complement_filter.php', + 'ezcTranslationContextWrite' => 'Translation/interfaces/context_write_interface.php', + 'ezcTranslationData' => 'Translation/structs/translation_data.php', + 'ezcTranslationLeetFilter' => 'Translation/filters/leet_filter.php', + 'ezcTranslationManager' => 'Translation/translation_manager.php', + 'ezcTranslationTsBackend' => 'Translation/backends/ts_backend.php', + 'ezcTranslationTsBackendOptions' => 'Translation/options/ts_backend.php', +); +?> diff --git a/include/ezcomponents/autoload/translation_cache_autoload.php b/include/ezcomponents/autoload/translation_cache_autoload.php new file mode 100644 index 000000000..44ef41cea --- /dev/null +++ b/include/ezcomponents/autoload/translation_cache_autoload.php @@ -0,0 +1,15 @@ + 'TranslationCacheTiein/backends/cache_backend.php', +); +?> diff --git a/include/ezcomponents/autoload/tree_autoload.php b/include/ezcomponents/autoload/tree_autoload.php new file mode 100644 index 000000000..05b3721db --- /dev/null +++ b/include/ezcomponents/autoload/tree_autoload.php @@ -0,0 +1,44 @@ + 'Tree/exceptions/exception.php', + 'ezcTreeDataStoreMissingDataException' => 'Tree/exceptions/missing_data.php', + 'ezcTreeIdsDoNotMatchException' => 'Tree/exceptions/ids_do_not_match.php', + 'ezcTreeInvalidClassException' => 'Tree/exceptions/invalid_class.php', + 'ezcTreeInvalidIdException' => 'Tree/exceptions/invalid_id.php', + 'ezcTreeInvalidXmlException' => 'Tree/exceptions/invalid_xml.php', + 'ezcTreeInvalidXmlFormatException' => 'Tree/exceptions/invalid_xml_format.php', + 'ezcTreeTransactionAlreadyStartedException' => 'Tree/exceptions/transaction_already_started.php', + 'ezcTreeTransactionNotStartedException' => 'Tree/exceptions/transaction_not_started.php', + 'ezcTreeUnknownIdException' => 'Tree/exceptions/unknown_id.php', + 'ezcTreeDataStore' => 'Tree/interfaces/data_store.php', + 'ezcTreeVisitable' => 'Tree/interfaces/visitable.php', + 'ezcTree' => 'Tree/tree.php', + 'ezcTreeVisitor' => 'Tree/interfaces/visitor.php', + 'ezcTreeXmlDataStore' => 'Tree/stores/xml.php', + 'ezcTreeMemory' => 'Tree/backends/memory.php', + 'ezcTreeMemoryDataStore' => 'Tree/stores/memory.php', + 'ezcTreeMemoryNode' => 'Tree/structs/memory_node.php', + 'ezcTreeNode' => 'Tree/tree_node.php', + 'ezcTreeNodeList' => 'Tree/tree_node_list.php', + 'ezcTreeNodeListIterator' => 'Tree/tree_node_list_iterator.php', + 'ezcTreeTransactionItem' => 'Tree/structs/transaction_item.php', + 'ezcTreeVisitorGraphViz' => 'Tree/visitors/graphviz.php', + 'ezcTreeVisitorPlainText' => 'Tree/visitors/plain_text.php', + 'ezcTreeVisitorXHTML' => 'Tree/visitors/xhtml.php', + 'ezcTreeVisitorXHTMLOptions' => 'Tree/options/visitor_xhtml.php', + 'ezcTreeVisitorYUI' => 'Tree/visitors/yui.php', + 'ezcTreeVisitorYUIOptions' => 'Tree/options/visitor_yui.php', + 'ezcTreeXml' => 'Tree/backends/xml.php', + 'ezcTreeXmlInternalDataStore' => 'Tree/stores/xml_internal.php', +); +?> diff --git a/include/ezcomponents/autoload/tree_db_autoload.php b/include/ezcomponents/autoload/tree_db_autoload.php new file mode 100644 index 000000000..6a4fa72fc --- /dev/null +++ b/include/ezcomponents/autoload/tree_db_autoload.php @@ -0,0 +1,21 @@ + 'TreeDatabaseTiein/exceptions/invalid_schema.php', + 'ezcTreeDb' => 'TreeDatabaseTiein/backends/db.php', + 'ezcTreeDbDataStore' => 'TreeDatabaseTiein/stores/db.php', + 'ezcTreeDbParentChild' => 'TreeDatabaseTiein/backends/db_parent_child.php', + 'ezcTreeDbExternalTableDataStore' => 'TreeDatabaseTiein/stores/db_external.php', + 'ezcTreeDbMaterializedPath' => 'TreeDatabaseTiein/backends/db_materialized_path.php', + 'ezcTreeDbNestedSet' => 'TreeDatabaseTiein/backends/db_nested_set.php', +); +?> diff --git a/include/ezcomponents/autoload/tree_persistent_autoload.php b/include/ezcomponents/autoload/tree_persistent_autoload.php new file mode 100644 index 000000000..7085c2861 --- /dev/null +++ b/include/ezcomponents/autoload/tree_persistent_autoload.php @@ -0,0 +1,15 @@ + 'TreePersistentObjectTiein/stores/persistent_object.php', +); +?> diff --git a/include/ezcomponents/autoload/url_autoload.php b/include/ezcomponents/autoload/url_autoload.php new file mode 100644 index 000000000..68dd2920d --- /dev/null +++ b/include/ezcomponents/autoload/url_autoload.php @@ -0,0 +1,22 @@ + 'Url/exceptions/url_exception.php', + 'ezcUrlInvalidParameterException' => 'Url/exceptions/url_invalid_parameter_exception.php', + 'ezcUrlNoConfigurationException' => 'Url/exceptions/url_no_configuration_exception.php', + 'ezcUrlNotRegisteredException' => 'Url/exceptions/url_not_registered_exception.php', + 'ezcUrl' => 'Url/url.php', + 'ezcUrlConfiguration' => 'Url/url_configuration.php', + 'ezcUrlCreator' => 'Url/url_creator.php', + 'ezcUrlTools' => 'Url/url_tools.php', +); +?> diff --git a/include/ezcomponents/autoload/webdav_autoload.php b/include/ezcomponents/autoload/webdav_autoload.php new file mode 100644 index 000000000..915d952b3 --- /dev/null +++ b/include/ezcomponents/autoload/webdav_autoload.php @@ -0,0 +1,112 @@ + 'Webdav/exceptions/exception.php', + 'ezcWebdavBadRequestException' => 'Webdav/exceptions/bad_request.php', + 'ezcWebdavBrokenRequestUriException' => 'Webdav/exceptions/broken_request_uri.php', + 'ezcWebdavHeadersNotValidatedException' => 'Webdav/exceptions/headers_not_validated.php', + 'ezcWebdavInvalidHeaderException' => 'Webdav/exceptions/invalid_header.php', + 'ezcWebdavInvalidHookException' => 'Webdav/exceptions/invalid_hook.php', + 'ezcWebdavInvalidRequestBodyException' => 'Webdav/exceptions/invalid_request_body.php', + 'ezcWebdavInvalidRequestMethodException' => 'Webdav/exceptions/invalid_request_method.php', + 'ezcWebdavMissingHeaderException' => 'Webdav/exceptions/missing_header.php', + 'ezcWebdavMissingServerVariableException' => 'Webdav/exceptions/misssing_server_variable.php', + 'ezcWebdavMissingTransportConfigurationException' => 'Webdav/exceptions/missing_transport_configuration.php', + 'ezcWebdavNotTransportHandlerException' => 'Webdav/exceptions/no_transport_handler.php', + 'ezcWebdavRequestNotSupportedException' => 'Webdav/exceptions/request_not_supported.php', + 'ezcWebdavUnknownHeaderException' => 'Webdav/exceptions/unknown_header.php', + 'ezcWebdavInfrastructureBase' => 'Webdav/interfaces/infrastructure_base.php', + 'ezcWebdavProperty' => 'Webdav/interfaces/property.php', + 'ezcWebdavBackend' => 'Webdav/interfaces/backend.php', + 'ezcWebdavBackendChange' => 'Webdav/interfaces/backend/change.php', + 'ezcWebdavBackendMakeCollection' => 'Webdav/interfaces/backend/make_collection.php', + 'ezcWebdavBackendPut' => 'Webdav/interfaces/backend/put.php', + 'ezcWebdavLiveProperty' => 'Webdav/interfaces/property_live.php', + 'ezcWebdavPropertyStorage' => 'Webdav/interfaces/property_storage.php', + 'ezcWebdavResponse' => 'Webdav/interfaces/response.php', + 'ezcWebdavBasicPropertyStorage' => 'Webdav/property_storages/basic.php', + 'ezcWebdavCopyResponse' => 'Webdav/responses/copy.php', + 'ezcWebdavDisplayInformation' => 'Webdav/structs/display_information.php', + 'ezcWebdavPathFactory' => 'Webdav/interfaces/path_factory.php', + 'ezcWebdavPluginConfiguration' => 'Webdav/plugin_configuration.php', + 'ezcWebdavPropFindResponse' => 'Webdav/responses/propfind.php', + 'ezcWebdavPropertyHandler' => 'Webdav/transports/property_handler.php', + 'ezcWebdavRequest' => 'Webdav/interfaces/request.php', + 'ezcWebdavSimpleBackend' => 'Webdav/backends/simple.php', + 'ezcWebdavSupportedLockPropertyLockentry' => 'Webdav/properties/supportedlock_lockentry.php', + 'ezcWebdavTransport' => 'Webdav/transport.php', + 'ezcWebdavAutomaticPathFactory' => 'Webdav/path_factories/automatic.php', + 'ezcWebdavBasicPathFactory' => 'Webdav/path_factories/basic.php', + 'ezcWebdavCollection' => 'Webdav/structs/collection.php', + 'ezcWebdavCopyRequest' => 'Webdav/requests/copy.php', + 'ezcWebdavCreationDateProperty' => 'Webdav/properties/creationdate.php', + 'ezcWebdavDateTime' => 'Webdav/tools/date_time.php', + 'ezcWebdavDeadProperty' => 'Webdav/properties/dead.php', + 'ezcWebdavDeleteRequest' => 'Webdav/requests/delete.php', + 'ezcWebdavDeleteResponse' => 'Webdav/responses/delete.php', + 'ezcWebdavDisplayNameProperty' => 'Webdav/properties/displayname.php', + 'ezcWebdavEmptyDisplayInformation' => 'Webdav/structs/display_information_empty.php', + 'ezcWebdavErrorResponse' => 'Webdav/responses/error.php', + 'ezcWebdavFileBackend' => 'Webdav/backends/file.php', + 'ezcWebdavFileBackendOptions' => 'Webdav/options/backend_file_options.php', + 'ezcWebdavFlaggedPropertyStorage' => 'Webdav/property_storages/flagged.php', + 'ezcWebdavGetCollectionResponse' => 'Webdav/responses/get_collection.php', + 'ezcWebdavGetContentLanguageProperty' => 'Webdav/properties/getcontentlanguage.php', + 'ezcWebdavGetContentLengthProperty' => 'Webdav/properties/getcontentlength.php', + 'ezcWebdavGetContentTypeProperty' => 'Webdav/properties/getcontenttype.php', + 'ezcWebdavGetEtagProperty' => 'Webdav/properties/getetag.php', + 'ezcWebdavGetLastModifiedProperty' => 'Webdav/properties/getlastmodified.php', + 'ezcWebdavGetRequest' => 'Webdav/requests/get.php', + 'ezcWebdavGetResourceResponse' => 'Webdav/responses/get_resource.php', + 'ezcWebdavHeadRequest' => 'Webdav/requests/head.php', + 'ezcWebdavHeadResponse' => 'Webdav/responses/head.php', + 'ezcWebdavHeaderHandler' => 'Webdav/transports/header_handler.php', + 'ezcWebdavLockDiscoveryProperty' => 'Webdav/properties/lockdiscovery.php', + 'ezcWebdavLockDiscoveryPropertyActiveLock' => 'Webdav/properties/lockdiscovery_activelock.php', + 'ezcWebdavLockRequest' => 'Webdav/requests/lock.php', + 'ezcWebdavMakeCollectionRequest' => 'Webdav/requests/mkcol.php', + 'ezcWebdavMakeCollectionResponse' => 'Webdav/responses/mkcol.php', + 'ezcWebdavMemoryBackend' => 'Webdav/backends/memory.php', + 'ezcWebdavMemoryBackendOptions' => 'Webdav/options/backend_memory_options.php', + 'ezcWebdavMicrosoftCompatibleTransport' => 'Webdav/transports/microsoft.php', + 'ezcWebdavMoveRequest' => 'Webdav/requests/move.php', + 'ezcWebdavMoveResponse' => 'Webdav/responses/move.php', + 'ezcWebdavMultistatusResponse' => 'Webdav/responses/multistatus.php', + 'ezcWebdavNamespaceRegistry' => 'Webdav/namespace_registry.php', + 'ezcWebdavNautilusPropertyHandler' => 'Webdav/transports/property_handlers/nautilus.php', + 'ezcWebdavOptionsRequest' => 'Webdav/requests/options.php', + 'ezcWebdavOptionsResponse' => 'Webdav/responses/options.php', + 'ezcWebdavOutputResult' => 'Webdav/structs/output_result.php', + 'ezcWebdavPluginParameters' => 'Webdav/plugin_parameters.php', + 'ezcWebdavPluginRegistry' => 'Webdav/plugin_registry.php', + 'ezcWebdavPropFindRequest' => 'Webdav/requests/propfind.php', + 'ezcWebdavPropPatchRequest' => 'Webdav/requests/proppatch.php', + 'ezcWebdavPropPatchResponse' => 'Webdav/responses/proppatch.php', + 'ezcWebdavPropStatResponse' => 'Webdav/responses/propstat.php', + 'ezcWebdavPutRequest' => 'Webdav/requests/put.php', + 'ezcWebdavPutResponse' => 'Webdav/responses/put.php', + 'ezcWebdavRequestLockInfoContent' => 'Webdav/requests/content/lock_info.php', + 'ezcWebdavRequestPropertyBehaviourContent' => 'Webdav/requests/content/property_behaviour.php', + 'ezcWebdavResource' => 'Webdav/structs/resource.php', + 'ezcWebdavResourceTypeProperty' => 'Webdav/properties/resourcetype.php', + 'ezcWebdavServer' => 'Webdav/server.php', + 'ezcWebdavServerConfiguration' => 'Webdav/server_configuration.php', + 'ezcWebdavServerConfigurationManager' => 'Webdav/server_configuration_manager.php', + 'ezcWebdavSourceProperty' => 'Webdav/properties/source.php', + 'ezcWebdavSourcePropertyLink' => 'Webdav/properties/source_link.php', + 'ezcWebdavStringDisplayInformation' => 'Webdav/structs/display_information_string.php', + 'ezcWebdavSupportedLockProperty' => 'Webdav/properties/supportedlock.php', + 'ezcWebdavUnlockRequest' => 'Webdav/requests/unlock.php', + 'ezcWebdavXmlDisplayInformation' => 'Webdav/structs/display_information_xml.php', + 'ezcWebdavXmlTool' => 'Webdav/tools/xml.php', +); +?> diff --git a/include/ezcomponents/autoload/workflow_autoload.php b/include/ezcomponents/autoload/workflow_autoload.php new file mode 100644 index 000000000..4e376398f --- /dev/null +++ b/include/ezcomponents/autoload/workflow_autoload.php @@ -0,0 +1,91 @@ + 'Workflow/exceptions/exception.php', + 'ezcWorkflowDefinitionStorageException' => 'Workflow/exceptions/definition_storage.php', + 'ezcWorkflowExecutionException' => 'Workflow/exceptions/execution.php', + 'ezcWorkflowInvalidInputException' => 'Workflow/exceptions/invalid_input.php', + 'ezcWorkflowInvalidWorkflowException' => 'Workflow/exceptions/invalid_workflow.php', + 'ezcWorkflowVisitable' => 'Workflow/interfaces/visitable.php', + 'ezcWorkflowNode' => 'Workflow/interfaces/node.php', + 'ezcWorkflowCondition' => 'Workflow/interfaces/condition.php', + 'ezcWorkflowNodeBranch' => 'Workflow/interfaces/node_branch.php', + 'ezcWorkflowNodeMerge' => 'Workflow/interfaces/node_merge.php', + 'ezcWorkflowConditionBooleanSet' => 'Workflow/interfaces/condition_boolean_set.php', + 'ezcWorkflowConditionComparison' => 'Workflow/interfaces/condition_comparison.php', + 'ezcWorkflowConditionType' => 'Workflow/interfaces/condition_type.php', + 'ezcWorkflowDefinitionStorage' => 'Workflow/interfaces/definition_storage.php', + 'ezcWorkflowExecution' => 'Workflow/interfaces/execution.php', + 'ezcWorkflowExecutionPlugin' => 'Workflow/interfaces/execution_plugin.php', + 'ezcWorkflowNodeArithmeticBase' => 'Workflow/interfaces/node_arithmetic_base.php', + 'ezcWorkflowNodeConditionalBranch' => 'Workflow/interfaces/node_conditional_branch.php', + 'ezcWorkflowNodeEnd' => 'Workflow/nodes/end.php', + 'ezcWorkflowNodeStart' => 'Workflow/nodes/start.php', + 'ezcWorkflowNodeSynchronization' => 'Workflow/nodes/control_flow/synchronization.php', + 'ezcWorkflowVisitor' => 'Workflow/interfaces/visitor.php', + 'ezcWorkflow' => 'Workflow/workflow.php', + 'ezcWorkflowConditionAnd' => 'Workflow/conditions/and.php', + 'ezcWorkflowConditionIsAnything' => 'Workflow/conditions/is_anything.php', + 'ezcWorkflowConditionIsArray' => 'Workflow/conditions/is_array.php', + 'ezcWorkflowConditionIsBool' => 'Workflow/conditions/is_bool.php', + 'ezcWorkflowConditionIsEqual' => 'Workflow/conditions/is_equal.php', + 'ezcWorkflowConditionIsEqualOrGreaterThan' => 'Workflow/conditions/is_equal_or_greater_than.php', + 'ezcWorkflowConditionIsEqualOrLessThan' => 'Workflow/conditions/is_equal_or_less_than.php', + 'ezcWorkflowConditionIsFalse' => 'Workflow/conditions/is_false.php', + 'ezcWorkflowConditionIsFloat' => 'Workflow/conditions/is_float.php', + 'ezcWorkflowConditionIsGreaterThan' => 'Workflow/conditions/is_greater_than.php', + 'ezcWorkflowConditionIsInteger' => 'Workflow/conditions/is_integer.php', + 'ezcWorkflowConditionIsLessThan' => 'Workflow/conditions/is_less_than.php', + 'ezcWorkflowConditionIsNotEqual' => 'Workflow/conditions/is_not_equal.php', + 'ezcWorkflowConditionIsObject' => 'Workflow/conditions/is_object.php', + 'ezcWorkflowConditionIsString' => 'Workflow/conditions/is_string.php', + 'ezcWorkflowConditionIsTrue' => 'Workflow/conditions/is_true.php', + 'ezcWorkflowConditionNot' => 'Workflow/conditions/not.php', + 'ezcWorkflowConditionOr' => 'Workflow/conditions/or.php', + 'ezcWorkflowConditionVariable' => 'Workflow/conditions/variable.php', + 'ezcWorkflowConditionVariables' => 'Workflow/conditions/variables.php', + 'ezcWorkflowConditionXor' => 'Workflow/conditions/xor.php', + 'ezcWorkflowDefinitionStorageXml' => 'Workflow/definition_storage/xml.php', + 'ezcWorkflowExecutionListener' => 'Workflow/interfaces/execution_listener.php', + 'ezcWorkflowExecutionListenerPlugin' => 'Workflow/execution/plugin/listener.php', + 'ezcWorkflowExecutionNonInteractive' => 'Workflow/execution/non_interactive.php', + 'ezcWorkflowExecutionVisualizerPlugin' => 'Workflow/execution/plugin/visualizer.php', + 'ezcWorkflowExecutionVisualizerPluginOptions' => 'Workflow/options/execution_plugin_visualizer.php', + 'ezcWorkflowNodeAction' => 'Workflow/nodes/action.php', + 'ezcWorkflowNodeCancel' => 'Workflow/nodes/cancel.php', + 'ezcWorkflowNodeDiscriminator' => 'Workflow/nodes/control_flow/discriminator.php', + 'ezcWorkflowNodeExclusiveChoice' => 'Workflow/nodes/control_flow/exclusive_choice.php', + 'ezcWorkflowNodeFinally' => 'Workflow/nodes/finally.php', + 'ezcWorkflowNodeInput' => 'Workflow/nodes/variables/input.php', + 'ezcWorkflowNodeLoop' => 'Workflow/nodes/control_flow/loop.php', + 'ezcWorkflowNodeMultiChoice' => 'Workflow/nodes/control_flow/multi_choice.php', + 'ezcWorkflowNodeParallelSplit' => 'Workflow/nodes/control_flow/parallel_split.php', + 'ezcWorkflowNodeSimpleMerge' => 'Workflow/nodes/control_flow/simple_merge.php', + 'ezcWorkflowNodeSubWorkflow' => 'Workflow/nodes/sub_workflow.php', + 'ezcWorkflowNodeSynchronizingMerge' => 'Workflow/nodes/control_flow/synchronizing_merge.php', + 'ezcWorkflowNodeVariableAdd' => 'Workflow/nodes/variables/add.php', + 'ezcWorkflowNodeVariableDecrement' => 'Workflow/nodes/variables/decrement.php', + 'ezcWorkflowNodeVariableDiv' => 'Workflow/nodes/variables/div.php', + 'ezcWorkflowNodeVariableIncrement' => 'Workflow/nodes/variables/increment.php', + 'ezcWorkflowNodeVariableMul' => 'Workflow/nodes/variables/mul.php', + 'ezcWorkflowNodeVariableSet' => 'Workflow/nodes/variables/set.php', + 'ezcWorkflowNodeVariableSub' => 'Workflow/nodes/variables/sub.php', + 'ezcWorkflowNodeVariableUnset' => 'Workflow/nodes/variables/unset.php', + 'ezcWorkflowServiceObject' => 'Workflow/interfaces/service_object.php', + 'ezcWorkflowUtil' => 'Workflow/util.php', + 'ezcWorkflowVariableHandler' => 'Workflow/interfaces/variable_handler.php', + 'ezcWorkflowVisitorNodeCollector' => 'Workflow/visitors/node_collector.php', + 'ezcWorkflowVisitorVerification' => 'Workflow/visitors/verification.php', + 'ezcWorkflowVisitorVisualization' => 'Workflow/visitors/visualization.php', + 'ezcWorkflowVisitorVisualizationOptions' => 'Workflow/options/visitor_visualization.php', +); +?> diff --git a/include/ezcomponents/autoload/workflow_database_autoload.php b/include/ezcomponents/autoload/workflow_database_autoload.php new file mode 100644 index 000000000..4bc7a1935 --- /dev/null +++ b/include/ezcomponents/autoload/workflow_database_autoload.php @@ -0,0 +1,18 @@ + 'WorkflowDatabaseTiein/definition_storage.php', + 'ezcWorkflowDatabaseExecution' => 'WorkflowDatabaseTiein/execution.php', + 'ezcWorkflowDatabaseUtil' => 'WorkflowDatabaseTiein/util.php', +); +?> diff --git a/include/ezcomponents/autoload/workflow_event_autoload.php b/include/ezcomponents/autoload/workflow_event_autoload.php new file mode 100644 index 000000000..1e2645291 --- /dev/null +++ b/include/ezcomponents/autoload/workflow_event_autoload.php @@ -0,0 +1,16 @@ + 'WorkflowEventLogTiein/listener.php', +); +?> diff --git a/include/ezcomponents/autoload/workflow_signal_autoload.php b/include/ezcomponents/autoload/workflow_signal_autoload.php new file mode 100644 index 000000000..a018f05dc --- /dev/null +++ b/include/ezcomponents/autoload/workflow_signal_autoload.php @@ -0,0 +1,17 @@ + 'WorkflowSignalSlotTiein/plugin.php', + 'ezcWorkflowSignalSlotPluginOptions' => 'WorkflowSignalSlotTiein/options/plugin.php', + 'ezcWorkflowSignalSlotReturnValue' => 'WorkflowSignalSlotTiein/structs/return_value.php', +); +?> diff --git a/include/ezcomponents/descriptions.txt b/include/ezcomponents/descriptions.txt new file mode 100644 index 000000000..f50e4f3a6 --- /dev/null +++ b/include/ezcomponents/descriptions.txt @@ -0,0 +1,219 @@ +Archive +------- +The component allows you to create, modify, and extract archive files of +various formats. The currently supported archives formats are Tar (with +the flavours: ustar, v7, pax, and gnu) and Zip. + +Authentication +-------------- +The purpose of the Authentication component is to provide support for different +means of identification and authentication of users using different providers and +protocols. + +AuthenticationDatabaseTiein +--------------------------- +The purpose of the Authentication component is to provide support for different +means of identification and authentication of users using different providers and +protocols. + +Base +---- +The Base package provides the basic infrastructure that all packages rely on. +Therefore every component relies on this package. + +Cache +----- +A solution for caching, supporting multiple backends allowing you to specify +the best performing solution for your caching-problem. + +Configuration +------------- +A component that allows you to use configuration files in different formats. +The formats include the standard .ini file, and an array based format. + +ConsoleTools +------------ +A set of classes to do different actions with the console (also called shell). +It can render a progress bar, tables and a status bar and contains a class for +parsing command line options. + +Database +-------- +A lightweight database layer on top of PHP's PDO that allows you to utilize a +database without having to take care of differences in SQL dialects. + +DatabaseSchema +-------------- +A set of classes that allow you to extract information from a database schema, +compare database schemas and apply a set of changes to a database schema. + +Debug +----- +This component provides a set of classes that help you to debug an +application. It provides timers and report generators for different formats +that give a summary of warnings and errors that occurred within your +application. + +Document +-------- +The Document components provides a general conversion framework for different +semantic document markup languages like XHTML, Docbook, RST and similar. + +EventLog +-------- +Allows you to log events or audit trails into files or other storage spaces in +different formats. + +EventLogDatabaseTiein +--------------------- +Contains the database writer backend for the EventLog component. + +Execution +--------- +Provides functionality to give feedback to your application's users when a +fatal error happened or an uncaught exception was thrown. + +Feed +---- +This component handles parsing and creating RSS1, RSS2 and ATOM feeds, with +support for different feed modules (dc, content, creativeCommons, geo, iTunes). + +File +---- +Provides support for file operations which are not covered by PHP or are just +missing. + +Graph +----- +A component for creating pie charts, line graphs and other kinds of diagrams. + +GraphDatabaseTiein +------------------ +The GraphDatabaseTiein provides functionality to directly use PDO statements +as basis for ezcGraph Datasets. + +ImageAnalysis +------------- +This class allows you to analyse image files in different ways. At least the +MIME type of the file is returned. In some cases (JPEG, TIFF and GIF) +additional information is gathered as well. + +ImageConversion +--------------- +A set of classes to apply different filters on images, such as colour changes, +resizing and special effects. + +Mail +---- +The component allows you construct and/or parse Mail messages conforming to +the mail standard. It has support for attachments, multipart messages and HTML +mail. It also interfaces with SMTP to send mail or IMAP, POP3 or mbox to +retrieve e-mail. + +PersistentObject +---------------- +This component allows you to store an arbitrary data structures to a fixed +database table. The component provides all the functionality needed to fetch, +list, delete etc these datastructures. + +PersistentObjectDatabaseSchemaTiein +----------------------------------- +This component allows the automatic generation of PersistentObject definition +files from DatabaseSchema definitions. + +PhpGenerator +------------ +Provides a simple interface for creating PHP files and executing PHP code. + +Search +------ +The Search component provides an interface to index and query documents +with different search engine backends. + +SignalSlot +---------- +The SignalSlot component implements a mechanism for inter and intra object +communication through the use of signals and slots. + +SystemInformation +----------------- +Provides access to common system variables, such as CPU type and speed, and the +available amount of memory. + +Template +-------- +A fully functional Templating system, supporting template compilation in +different levels, user defined functions and operators, an optimizer, output +escaping for different output handlers to prevent XSS and other security +problems and a plug in system for extra functionality (such as a Translation +system). + +TemplateTranslationTiein +------------------------ +Provides functionality to use translations inside templates. + +Translation +----------- +A component that reads XML translation definitions (the Qt Linguist format), +supports caching of translation contexts and presents you with a class to apply +translations to strings. A filter system allows you to transform translation +definitions for special use. + +TranslationCacheTiein +--------------------- +This component adds the TranslationCache backend to the Translation component +and allows cached translations. + +Tree +---- +The Tree component handles the creating, manipulating and querying of tree +structures. The component supports different storage algorithms for optimal +performance. + +TreeDatabaseTiein +----------------- +The Tree component handles the creating, manipulating and querying of tree +structures. This component implements the database related backends and +data stores. + +TreePersistentObjectTiein +------------------------- +The Tree component handles the creating, manipulating and querying of tree +structures. This component uses persistent objects as data storage for the +data elements of the tree nodes. + +Url +--- +The Url package provides basic operations to handle urls (parse, build, +get/set path, get/set query). + +UserInput +--------- +A component that assists you to safely user input variables coming into your +application. It builds on top of PHP's filter extension and extends it by +providing a more inituitive API. + +Webdav +------ +This component allows you to set up and run your own WebDAV (RFC 2518) server, +to enable online content editing for the users of your system through the +WebDAV HTTP extension. + +Workflow +-------- +The purpose of the Workflow component is to provide the core functionality of an +activity-based workflow system including the definition and execution of +workflow specifications. + +WorkflowDatabaseTiein +--------------------- +Contains the database backend for the Workflow component. + +WorkflowEventLogTiein +--------------------- +Contains the EventLog listener for the Workflow component. + +WorkflowSignalSlotTiein +----------------------- +Contains the SignalSlot links for the Workflow component. +