diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index 06f3fecc0eab27563bf653fec14ba32f361fc568..8219ee2c30d461f8fa1e6b3cd02950e3ff8eb7cf 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -118,7 +118,7 @@ $config = [ 'VuFind\Controller\CombinedController' => 'VuFind\Controller\AbstractBaseFactory', 'VuFind\Controller\ConfirmController' => 'VuFind\Controller\AbstractBaseFactory', 'VuFind\Controller\ContentController' => 'VuFind\Controller\AbstractBaseFactory', - 'VuFind\Controller\CoverController' => 'VuFind\Controller\AbstractBaseFactory', + 'VuFind\Controller\CoverController' => 'VuFind\Controller\CoverControllerFactory', 'VuFind\Controller\EdsController' => 'VuFind\Controller\AbstractBaseFactory', 'VuFind\Controller\EdsrecordController' => 'VuFind\Controller\AbstractBaseFactory', 'VuFind\Controller\EITController' => 'VuFind\Controller\AbstractBaseFactory', @@ -309,6 +309,10 @@ $config = [ 'VuFind\ContentBlock\BlockLoader' => 'VuFind\ContentBlock\BlockLoaderFactory', 'VuFind\ContentBlock\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', 'VuFind\Cookie\CookieManager' => 'VuFind\Cookie\CookieManagerFactory', + 'VuFind\Cover\CachingProxy' => 'VuFind\Cover\CachingProxyFactory', + 'VuFind\Cover\Generator' => 'VuFind\Cover\GeneratorFactory', + 'VuFind\Cover\Layer\PluginManager' => 'VuFind\ServiceManager\AbstractPluginManagerFactory', + 'VuFind\Cover\Loader' => 'VuFind\Cover\LoaderFactory', 'VuFind\Cover\Router' => 'VuFind\Cover\RouterFactory', 'VuFind\Crypt\HMAC' => 'VuFind\Crypt\HMACFactory', 'VuFind\Date\Converter' => 'VuFind\Service\DateConverterFactory', @@ -483,6 +487,7 @@ $config = [ 'content_summaries' => [ /* see VuFind\Content\Summaries\PluginManager for defaults */ ], 'content_toc' => [ /* see VuFind\Content\TOC\PluginManager for defaults */ ], 'contentblock' => [ /* see VuFind\ContentBlock\PluginManager for defaults */ ], + 'cover_layer' => [ /* see VuFind\Cover\Layer\PluginManager for defaults */ ], 'db_row' => [ /* see VuFind\Db\Row\PluginManager for defaults */ ], 'db_table' => [ /* see VuFind\Db\Table\PluginManager for defaults */ ], 'hierarchy_driver' => [ /* see VuFind\Hierarchy\Driver\PluginManager for defaults */ ], diff --git a/module/VuFind/src/VuFind/Controller/CoverController.php b/module/VuFind/src/VuFind/Controller/CoverController.php index e50be8caf7087a97a28278e31cedc561e7284995..c4d3b6770689de0de2db8989079236d5e41b6a2d 100644 --- a/module/VuFind/src/VuFind/Controller/CoverController.php +++ b/module/VuFind/src/VuFind/Controller/CoverController.php @@ -29,6 +29,7 @@ namespace VuFind\Controller; use VuFind\Cover\CachingProxy; use VuFind\Cover\Loader; +use VuFind\Session\Settings as SessionSettings; /** * Generates covers for book entries @@ -39,73 +40,42 @@ use VuFind\Cover\Loader; * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License * @link https://vufind.org Main Page */ -class CoverController extends AbstractBase +class CoverController extends \Zend\Mvc\Controller\AbstractActionController { /** * Cover loader * * @var Loader */ - protected $loader = false; + protected $loader; /** - * Caching proxy + * Proxy loader * * @var CachingProxy */ - protected $proxy = false; + protected $proxy; /** - * Get the cover cache directory + * Session settings * - * @return string + * @var SessionSettings */ - protected function getCacheDir() - { - return $this->serviceLocator->get('VuFind\Cache\Manager') - ->getCache('cover')->getOptions()->getCacheDir(); - } + protected $sessionSettings = null; /** - * Get the cover loader object + * Constructor * - * @return Loader + * @param Loader $loader Cover loader + * @param CachingProxy $proxy Proxy loader + * @param SessionSettings $ss Session settings */ - protected function getLoader() - { - // Construct object for loading cover images if it does not already exist: - if (!$this->loader) { - $cacheDir = $this->getCacheDir(); - $this->loader = new Loader( - $this->getConfig(), - $this->serviceLocator->get('VuFind\Content\Covers\PluginManager'), - $this->serviceLocator->get('VuFindTheme\ThemeInfo'), - $this->serviceLocator->get('VuFindHttp\HttpService'), - $cacheDir - ); - $initializer = new \VuFind\ServiceManager\ServiceInitializer(); - $initializer($this->serviceLocator, $this->loader); - } - return $this->loader; - } - - /** - * Get the caching proxy object - * - * @return CachingProxy - */ - protected function getProxy() - { - if (!$this->proxy) { - $client = $this->serviceLocator->get('VuFindHttp\HttpService') - ->createClient(); - $cacheDir = $this->getCacheDir() . '/proxy'; - $config = $this->getConfig()->toArray(); - $whitelist = isset($config['Content']['coverproxyCache']) - ? (array)$config['Content']['coverproxyCache'] : []; - $this->proxy = new CachingProxy($client, $cacheDir, $whitelist); - } - return $this->proxy; + public function __construct(Loader $loader, CachingProxy $proxy, + SessionSettings $ss + ) { + $this->loader = $loader; + $this->proxy = $proxy; + $this->sessionSettings = $ss; } /** @@ -139,15 +109,15 @@ class CoverController extends AbstractBase */ public function showAction() { - $this->disableSessionWrites(); // avoid session write timing bug + $this->sessionSettings->disableWrite(); // avoid session write timing bug // Special case: proxy a full URL: - $proxy = $this->params()->fromQuery('proxy'); - if (!empty($proxy)) { + $url = $this->params()->fromQuery('proxy'); + if (!empty($url)) { try { - $image = $this->getProxy()->fetch($proxy); + $image = $this->proxy->fetch($url); return $this->displayImage( - $image->getHeaders()->get('contenttype')->getFieldValue(), + $image->getHeaders()->get('content-type')->getFieldValue(), $image->getContent() ); } catch (\Exception $e) { @@ -157,7 +127,7 @@ class CoverController extends AbstractBase } // Default case -- use image loader: - $this->getLoader()->loadImage($this->getImageParams()); + $this->loader->loadImage($this->getImageParams()); return $this->displayImage(); } @@ -168,8 +138,8 @@ class CoverController extends AbstractBase */ public function unavailableAction() { - $this->disableSessionWrites(); // avoid session write timing bug - $this->getLoader()->loadUnavailable(); + $this->sessionSettings->disableWrite(); // avoid session write timing bug + $this->loader->loadUnavailable(); return $this->displayImage(); } @@ -187,7 +157,7 @@ class CoverController extends AbstractBase $response = $this->getResponse(); $headers = $response->getHeaders(); $headers->addHeaderLine( - 'Content-type', $type ?: $this->getLoader()->getContentType() + 'Content-type', $type ?: $this->loader->getContentType() ); // Send proper caching headers so that the user's browser @@ -205,7 +175,7 @@ class CoverController extends AbstractBase 'Expires', gmdate('D, d M Y H:i:s', time() + $coverImageTtl) . ' GMT' ); - $response->setContent($image ?: $this->getLoader()->getImage()); + $response->setContent($image ?: $this->loader->getImage()); return $response; } } diff --git a/module/VuFind/src/VuFind/Controller/CoverControllerFactory.php b/module/VuFind/src/VuFind/Controller/CoverControllerFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..26de632cd2a86d138aa8747a1ccb5e1438d643bd --- /dev/null +++ b/module/VuFind/src/VuFind/Controller/CoverControllerFactory.php @@ -0,0 +1,70 @@ +<?php +/** + * Cover controller factory. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Controller + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Controller; + +use Interop\Container\ContainerInterface; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Cover controller factory. + * + * @category VuFind + * @package Controller + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class CoverControllerFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + return new $requestedName( + $container->get('VuFind\Cover\Loader'), + $container->get('VuFind\Cover\CachingProxy'), + $container->get('VuFind\Session\Settings') + ); + } +} diff --git a/module/VuFind/src/VuFind/Cover/CachingProxyFactory.php b/module/VuFind/src/VuFind/Cover/CachingProxyFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..068783abfdeaa8c7fb261285787aa5be2f986555 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/CachingProxyFactory.php @@ -0,0 +1,73 @@ +<?php +/** + * Cover caching proxy factory. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Cover; + +use Interop\Container\ContainerInterface; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Cover caching proxy factory. + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class CachingProxyFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + $cacheDir = $container->get('VuFind\Cache\Manager') + ->getCache('cover')->getOptions()->getCacheDir(); + $client = $container->get('VuFindHttp\HttpService')->createClient(); + $config = $container->get('VuFind\Config\PluginManager')->get('config') + ->toArray(); + $whitelist = isset($config['Content']['coverproxyCache']) + ? (array)$config['Content']['coverproxyCache'] : []; + return new $requestedName($client, $cacheDir . '/proxy', $whitelist); + } +} diff --git a/module/VuFind/src/VuFind/Cover/Generator.php b/module/VuFind/src/VuFind/Cover/Generator.php index b3b69afbff0103c4e6c4e31fb200b452c806f6b2..299f5498b7600093bc83d282b156456f64f66ef4 100644 --- a/module/VuFind/src/VuFind/Cover/Generator.php +++ b/module/VuFind/src/VuFind/Cover/Generator.php @@ -27,6 +27,10 @@ */ namespace VuFind\Cover; +use VuFind\Cover\Layer\LayerInterface; +use VuFind\Cover\Layer\PluginManager as LayerManager; +use VuFindTheme\ThemeInfo; + /** * Dynamic Book Cover Generator * @@ -39,46 +43,40 @@ namespace VuFind\Cover; class Generator { /** - * Style configuration + * Default settings (values used by setOptions() if not overridden). * * @var array */ - protected $settings = []; - - /** - * Base color used to fill initially created image. - * - * @var int - */ - protected $baseColor; - - /** - * Title's fill color - * - * @var int - */ - protected $titleFillColor; - - /** - * Title's border color - * - * @var int - */ - protected $titleBorderColor; - - /** - * Author's fill color - * - * @var int - */ - protected $authorFillColor; - - /** - * Author's border color - * - * @var int - */ - protected $authorBorderColor; + protected $defaultSettings = [ + 'backgroundMode' => 'grid', + 'textMode' => 'default', + 'authorFont' => 'DroidSerif-Bold.ttf', + 'titleFontSize' => 7, + 'authorFontSize' => 6, + 'lightness' => 220, + 'maxTitleLines' => 5, + 'minAuthorFontSize' => 5, + 'saturation' => 80, + 'size' => 84, + 'textAlign' => 'center', + 'titleFont' => 'DroidSerif-Bold.ttf', + 'topPadding' => 19, + 'bottomPadding' => 3, + 'wrapWidth' => 80, + 'titleFillColor' => 'black', + 'titleBorderColor' => 'none', + 'authorFillColor' => 'white', + 'authorBorderColor' => 'black', + 'baseColor' => 'white', + 'accentColor' => 'random', + ]; + + /** + * Active style configuration + * + * @var object + */ + protected $settings; /** * Base for image @@ -88,75 +86,63 @@ class Generator protected $im; /** - * Width of image (pixels) + * ThemeInfo object * - * @var int + * @var ThemeInfo */ - protected $width; + protected $themeTools; /** - * Height of image (pixels) + * Layer manager * - * @var int + * @var LayerManager */ - protected $height; + protected $layerManager; /** * Constructor * - * @param \VuFindTheme\ThemeInfo $themeTools For font loading - * @param array $settings Overwrite styles + * @param ThemeInfo $themeTools For font loading + * @param LayerManager $lm Layer manager + * @param array $settings Overwrite styles */ - public function __construct($themeTools, $settings = []) - { + public function __construct(ThemeInfo $themeTools, LayerManager $lm, + array $settings = [] + ) { $this->themeTools = $themeTools; - $default = [ - 'backgroundMode' => 'grid', - 'textMode' => 'default', - 'authorFont' => 'DroidSerif-Bold.ttf', - 'titleFontSize' => 7, - 'authorFontSize' => 6, - 'lightness' => 220, - 'maxTitleLines' => 5, - 'minAuthorFontSize' => 5, - 'saturation' => 80, - 'size' => 84, - 'textAlign' => 'center', - 'titleFont' => 'DroidSerif-Bold.ttf', - 'topPadding' => 19, - 'bottomPadding' => 3, - 'wrapWidth' => 80, - 'titleFillColor' => 'black', - 'titleBorderColor' => 'none', - 'authorFillColor' => 'white', - 'authorBorderColor' => 'black', - 'baseColor' => 'white', - 'accentColor' => 'random', - ]; - foreach ($settings as $i => $setting) { - $default[$i] = $setting; - } - $default['authorFont'] = $this->fontPath($default['authorFont']); - $default['titleFont'] = $this->fontPath($default['titleFont']); - $this->settings = (object)$default; - $this->initImage(); - $this->initColors(); + $this->layerManager = $lm; + $this->setOptions($settings); } /** - * Initialize colors to be used in the image. + * Set the generator options. + * + * @param array $rawSettings Overwrite styles * * @return void */ - protected function initColors() + public function setOptions($rawSettings) { - $this->baseColor = $this->getColor($this->settings->baseColor); - $this->titleFillColor = $this->getColor($this->settings->titleFillColor); - $this->titleBorderColor = $this->getColor($this->settings->titleBorderColor); - $this->authorFillColor = $this->getColor($this->settings->authorFillColor); - $this->authorBorderColor = $this->getColor( - $this->settings->authorBorderColor - ); + // Merge incoming settings with defaults: + $settings = $rawSettings + $this->defaultSettings; + + // Adjust font paths: + $settings['authorFont'] = $this->fontPath($settings['authorFont']); + $settings['titleFont'] = $this->fontPath($settings['titleFont']); + + // Determine final dimensions: + $parts = explode('x', strtolower($settings['size'])); + if (count($parts) < 2) { + $settings['width'] = $settings['height'] = $parts[0]; + } else { + list($settings['width'], $settings['height']) = $parts; + } + + // Store the results as an object: + $this->settings = (object)$settings; + + // Reinitialize everything based on settings: + $this->initImage(); } /** @@ -167,14 +153,9 @@ class Generator protected function initImage() { // Create image - $parts = explode('x', strtolower($this->settings->size)); - if (count($parts) < 2) { - $this->width = $this->height = $parts[0]; - } else { - list($this->width, $this->height) = $parts; - } - if (!($this->im = imagecreate($this->width, $this->height))) { - throw new \Exception("Cannot Initialize new GD image stream"); + $this->im = imagecreate($this->settings->width, $this->settings->height); + if (!$this->im) { + throw new \Exception('Cannot Initialize new GD image stream'); } } @@ -202,59 +183,6 @@ class Generator return $img; } - /** - * Check and allocates color - * - * @param string $color Legal color name from HTML4 - * - * @return allocated color - */ - protected function getColor($color) - { - switch (strtolower($color)) { - case 'black': - return imagecolorallocate($this->im, 0, 0, 0); - case 'silver': - return imagecolorallocate($this->im, 192, 192, 192); - case 'gray': - return imagecolorallocate($this->im, 128, 128, 128); - case 'white': - return imagecolorallocate($this->im, 255, 255, 255); - case 'maroon': - return imagecolorallocate($this->im, 128, 0, 0); - case 'red': - return imagecolorallocate($this->im, 255, 0, 0); - case 'purple': - return imagecolorallocate($this->im, 128, 0, 128); - case 'fuchsia': - return imagecolorallocate($this->im, 255, 0, 255); - case 'green': - return imagecolorallocate($this->im, 0, 128, 0); - case 'lime': - return imagecolorallocate($this->im, 0, 255, 0); - case 'olive': - return imagecolorallocate($this->im, 128, 128, 0); - case 'yellow': - return imagecolorallocate($this->im, 255, 255, 0); - case 'navy': - return imagecolorallocate($this->im, 0, 0, 128); - case 'blue': - return imagecolorallocate($this->im, 0, 0, 255); - case 'teal': - return imagecolorallocate($this->im, 0, 128, 128); - case 'aqua': - return imagecolorallocate($this->im, 0, 255, 255); - default: - if (substr($color, 0, 1) == '#' && strlen($color) == 7) { - $r = hexdec(substr($color, 1, 2)); - $g = hexdec(substr($color, 3, 2)); - $b = hexdec(substr($color, 5, 2)); - return imagecolorallocate($this->im, $r, $g, $b); - } - return false; - } - } - /** * Generates a dynamic cover image from elements of the item * @@ -266,12 +194,11 @@ class Generator */ public function generate($title, $author, $callnumber = null) { - // Generate seed from callnumber, title back up - $seed = $this->createSeed($title, $callnumber); + $details = compact('title', 'author', 'callnumber'); // Build the image - $this->drawBackgroundLayer($seed); - $this->drawTextLayer($title, $author); + $this->getBackgroundLayer()->render($this->im, $details, $this->settings); + $this->getTextLayer()->render($this->im, $details, $this->settings); // Render the image $png = $this->renderPng(); @@ -280,311 +207,28 @@ class Generator } /** - * Draw the background behind the text. - * - * @param int $seed Seed value + * Get the layer plugin for the background * - * @return void + * @return LayerInterface */ - protected function drawBackgroundLayer($seed) + protected function getBackgroundLayer() { - // Construct a method name using the mode setting; if the method is not - // defined, use the default drawGridBackground(). - $mode = ucwords(strtolower($this->settings->backgroundMode)); - $method = "draw{$mode}Background"; - return method_exists($this, $method) - ? $this->$method($seed) : $this->drawGridBackground($seed); - } - - /** - * Position the text on the image. - * - * @param string $title Title of the book - * @param string $author Author of the book - * - * @return void - */ - protected function drawTextLayer($title, $author) - { - // Construct a method name using the mode setting; if the method is not - // defined, use the default drawGridBackground(). - $mode = ucwords(strtolower($this->settings->textMode)); - $method = "draw{$mode}Text"; - return method_exists($this, $method) - ? $this->$method($title, $author) - : $this->drawDefaultText($title, $author); - } - - /** - * Position the text on the image using default rules. - * - * @param string $title Title of the book - * @param string $author Author of the book - * - * @return void - */ - protected function drawDefaultText($title, $author) - { - if (null !== $title) { - $this->drawTitle($title, $this->height / 8); - } - if (null !== $author) { - $this->drawAuthor($author); - } - } - - /** - * Position the text on the image using "initials" rules. - * - * @param string $title Title of the book - * @param string $author Author of the book - * - * @return void - */ - protected function drawInitialText($title, $author) - { - // Get the first letter of title or author... - $initial = mb_substr($title . $author, 0, 1, 'UTF-8'); - - // Get the height of a character with no descenders: - $heightWithoutDescenders = $this->textHeight( - 'O', $this->settings->titleFont, $this->settings->titleFontSize - ); - - // Get the height of the currently selected character: - $textHeight = $this->textHeight( - $initial, $this->settings->titleFont, $this->settings->titleFontSize - ); - - // Draw the letter... Note that the way we are using $textHeight and - // $heightWithoutDescenders is something of a fudge driven by the fact - // that PHP measures text in total pixels, but positions text using the - // baseline (thus not accounting for descenders). To truly vertically - // center something, we need more information than we can get without - // using an extension or library to read more information from the font - // file. The formula here is not particularly well-informed but seems - // to produce acceptable results for many scenarios. - $this->drawText( - $initial, - $heightWithoutDescenders + ($this->height - $textHeight) / 2, - $this->settings->titleFont, - $this->settings->titleFontSize, - $this->titleFillColor, - $this->titleBorderColor, - $this->settings->textAlign + $service = strtolower($this->settings->backgroundMode) . 'background'; + return $this->layerManager->get( + $this->layerManager->has($service) ? $service : 'gridbackground' ); } /** - * Generate an accent color from a seed value. - * - * @param int $seed Seed value + * Get the layer plugin for the text * - * @return int + * @return LayerInterface */ - protected function getAccentColor($seed) + protected function getTextLayer() { - // Number to color, hsb to control saturation and lightness - if ($this->settings->accentColor == 'random') { - return $this->makeHSBColor( - $seed % 256, - $this->settings->saturation, - $this->settings->lightness - ); - } - return $this->getColor($this->settings->accentColor); - } - - /** - * Generates a solid color background, ala Google - * - * @param int $seed Seed value - * - * @return void - */ - protected function drawSolidBackground($seed) - { - // Fill solid color - imagefilledrectangle( - $this->im, - 0, - 0, - $this->width, - $this->height, - $this->getAccentColor($seed) - ); - } - - /** - * Generates a grid of colors as primary feature - * - * @param int $seed Seed value - * - * @return void - */ - protected function drawGridBackground($seed) - { - // Render the grid - $this->renderGrid($this->createPattern($seed), $this->getAccentColor($seed)); - } - - /** - * Generates a dynamic cover image from elements of the book - * - * @param string $title Title of the book - * @param string $callnumber Callnumber of the book - * - * @return int unique number for this record - */ - protected function createSeed($title, $callnumber) - { - // Turn callnumber into number - if (null == $callnumber) { - $callnumber = $title; - } - if (null !== $callnumber) { - $cv = 0; - for ($i = 0;$i < strlen($callnumber);$i++) { - $cv += ord($callnumber[$i]); - } - return $cv; - } else { - // If no callnumber, random - return ceil(rand(pow(2, 4), pow(2, 32))); - } - } - - /** - * Turn number into pattern - * - * @param int $seed Seed used to generate the pattern - * - * @return string binary string describing a quarter of the pattern - */ - protected function createPattern($seed) - { - // Convert to binary - $bc = decbin($seed); - // If we have less that a half of a quarter - if (strlen($bc) < 8) { - // Rotate square of the first 4 into a 4x2 - // Simulate matrix rotation on string - $bc = substr($bc, 0, 3) - . substr($bc, 0, 1) - . substr($bc, 2, 2) - . substr($bc, 3, 1) - . substr($bc, 1, 1); - } - // If we have less than a quarter - if (strlen($bc) < 16) { - // Rotate the first 8 as a 4x2 into a 4x4 - $bc .= strrev($bc); - } - return $bc; - } - - /** - * Render title in wrapped, black text with white border - * - * @param string $title Title to write - * @param int $lineHeight Pixels we move down each line - * - * @return void - */ - protected function drawTitle($title, $lineHeight) - { - $words = explode(' ', $title); - // Wrap words into image - // Add words until off image, go back and print - $line = ''; - $lineCount = 0; - $i = 0; - while ($i < count($words) - && $lineCount < $this->settings->maxTitleLines - 1 - ) { - $pline = $line; - // Format - $text = $words[$i]; - $line .= $text . ' '; - $textWidth = $this->textWidth( - rtrim($line, ' '), - $this->settings->titleFont, - $this->settings->titleFontSize - ); - if ($textWidth > $this->settings->wrapWidth) { - // Print black with white border - $this->drawText( - rtrim($pline, ' '), - $this->settings->topPadding + $lineHeight * $lineCount, - $this->settings->titleFont, - $this->settings->titleFontSize, - $this->titleFillColor, - $this->titleBorderColor - ); - $line = $text . ' '; - $lineCount++; - } - $i++; - } - // Print the last words - $this->drawText( - rtrim($line, ' '), - $this->settings->topPadding + $lineHeight * $lineCount, - $this->settings->titleFont, - $this->settings->titleFontSize, - $this->titleFillColor, - $this->titleBorderColor - ); - // Add ellipses if we've truncated - if ($i < count($words) - 1) { - $this->drawText( - '...', - $this->settings->topPadding - + $this->settings->maxTitleLines * $lineHeight, - $this->settings->titleFont, - $this->settings->titleFontSize + 1, - $this->titleFillColor, - $this->titleBorderColor - ); - } - } - - /** - * Render author at bottom in wrapped, white text with black border - * - * @param string $author Author to write - * - * @return void - */ - protected function drawAuthor($author) - { - // Scale author to fit by incrementing fontsizes down - $fontSize = $this->settings->authorFontSize + 1; - do { - $fontSize--; - $textWidth = $this->textWidth( - $author, - $this->settings->authorFont, - $fontSize - ); - } while ($textWidth > $this->settings->wrapWidth && - $fontSize > $this->settings->minAuthorFontSize - ); - // Too small to read? Align left - $textWidth = $this->textWidth( - $author, - $this->settings->authorFont, - $fontSize - ); - $align = $textWidth > $this->width ? 'left' : null; - $this->drawText( - $author, - $this->height - $this->settings->bottomPadding, - $this->settings->authorFont, - $fontSize, - $this->authorFillColor, - $this->authorBorderColor, - $align + $service = strtolower($this->settings->textMode) . 'text'; + return $this->layerManager->get( + $this->layerManager->has($service) ? $service : 'defaulttext' ); } @@ -602,171 +246,4 @@ class Generator $fileMatch = $this->themeTools->findContainingTheme($filenames, true); return empty($fileMatch) ? false : $fileMatch; } - - /** - * Returns the width a string would render to - * - * @param string $text Text to test - * @param string $font Full font path - * @param string $size Size of the font - * - * @return int - */ - protected function textWidth($text, $font, $size) - { - $p = imagettfbbox($size, 0, $font, $text); - return $p[2] - $p[0]; - } - - /** - * Returns the height a string would render to - * - * @param string $text Text to test - * @param string $font Full font path - * @param string $size Size of the font - * - * @return int - */ - protected function textHeight($text, $font, $size) - { - $p = imagettfbbox($size, 0, $font, $text); - return $p[1] - $p[5]; - } - - /** - * Simulate outlined text - * - * @param string $text Text to render - * @param int $y Top position - * @param string $font Full path to font - * @param int $fontSize Size of the font - * @param int $mcolor Main text color - * @param int $scolor Secondary border color - * @param string $align 'left','center','right' - * - * @return void - */ - protected function drawText($text, $y, $font, $fontSize, $mcolor, - $scolor = false, $align = null - ) { - // If the wrap width is smaller than the image width, we want to account - // for this when right or left aligning to maintain padding on the image. - $wrapGap = ($this->width - $this->settings->wrapWidth) / 2; - - $textWidth = $this->textWidth( - $text, - $font, - $fontSize - ); - if ($textWidth > $this->width) { - $align = 'left'; - $wrapGap = 0; // kill wrap gap to maximize text fit - } - if (null == $align) { - $align = $this->settings->textAlign; - } - if ($align == 'left') { - $x = $wrapGap; - } - if ($align == 'center') { - $x = ($this->width - $textWidth) / 2; - } - if ($align == 'right') { - $x = $this->width - ($textWidth + $wrapGap); - } - - // Generate 5 lines of text, 4 offset in a border color - if ($scolor) { - imagettftext( - $this->im, $fontSize, 0, $x, $y + 1, $scolor, $font, $text - ); - imagettftext( - $this->im, $fontSize, 0, $x, $y - 1, $scolor, $font, $text - ); - imagettftext( - $this->im, $fontSize, 0, $x + 1, $y, $scolor, $font, $text - ); - imagettftext( - $this->im, $fontSize, 0, $x - 1, $y, $scolor, $font, $text - ); - } - // 1 centered in main color - imagettftext($this->im, $fontSize, 0, $x, $y, $mcolor, $font, $text); - } - - /** - * Convert 16 long binary string to 8x8 color grid - * Reflects vertically and horizontally - * - * @param string $bc Binary string of pattern - * @param int $color Fill color - * - * @return void - */ - protected function renderGrid($bc, $color) - { - $halfWidth = $this->width / 2; - $halfHeight = $this->height / 2; - $boxWidth = $this->width / 8; - $boxHeight = $this->height / 8; - - $bc = str_split($bc); - for ($k = 0;$k < 4;$k++) { - $x = $k % 2 ? $halfWidth : $halfWidth - $boxWidth; - $y = $k / 2 < 1 ? $halfHeight : $halfHeight - $boxHeight; - $u = $k % 2 ? $boxWidth : -$boxWidth; - $v = $k / 2 < 1 ? $boxHeight : -$boxHeight; - for ($i = 0;$i < 16;$i++) { - if ($bc[$i] == "1") { - imagefilledrectangle( - $this->im, $x, $y, - $x + $boxWidth - 1, $y + $boxHeight - 1, $color - ); - } - $x += $u; - if ($x >= $this->width || $x < 0) { - $x = $k % 2 ? $halfWidth : $halfWidth - $boxWidth; - $y += $v; - } - } - } - //imagefilledrectangle($this->im,0,$size-11,$size-1,$size,$color); - } - - /** - * Using HSB allows us to control the contrast while allowing randomness - * - * @param int $h Hue (0-255) - * @param int $s Saturation (0-255) - * @param int $v Lightness (0-255) - * - * @return int - */ - protected function makeHSBColor($h, $s, $v) - { - $s /= 256.0; - if ($s == 0.0) { - return imagecolorallocate($this->im, $v, $v, $v); - } - $h /= (256.0 / 6.0); - $i = floor($h); - $f = $h - $i; - $p = (int)($v * (1.0 - $s)); - $q = (int)($v * (1.0 - $s * $f)); - $t = (int)($v * (1.0 - $s * (1.0 - $f))); - switch ($i) { - case 0: - return imagecolorallocate($this->im, $v, $t, $p); - case 1: - return imagecolorallocate($this->im, $q, $v, $p); - case 2: - return imagecolorallocate($this->im, $p, $v, $t); - case 3: - return imagecolorallocate($this->im, $p, $q, $v); - case 4: - return imagecolorallocate($this->im, $t, $p, $v); - default: - return imagecolorallocate($this->im, $v, $p, $q); - } - } } diff --git a/module/VuFind/src/VuFind/Cover/GeneratorFactory.php b/module/VuFind/src/VuFind/Cover/GeneratorFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..36938fc0dde5cf5e5acb089d6f3f27b199aa35cd --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/GeneratorFactory.php @@ -0,0 +1,69 @@ +<?php +/** + * Cover generator factory. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Cover; + +use Interop\Container\ContainerInterface; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Cover generator factory. + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class GeneratorFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + return new $requestedName( + $container->get('VuFindTheme\ThemeInfo'), + $container->get('VuFind\Cover\Layer\PluginManager') + ); + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/AbstractBackgroundLayer.php b/module/VuFind/src/VuFind/Cover/Layer/AbstractBackgroundLayer.php new file mode 100644 index 0000000000000000000000000000000000000000..c529094743d7ca2d0d60b28bc7e62f318f7bba29 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/AbstractBackgroundLayer.php @@ -0,0 +1,84 @@ +<?php +/** + * Abstract cover background layer + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Abstract cover background layer + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +abstract class AbstractBackgroundLayer extends AbstractLayer +{ + /** + * Generates a dynamic cover image from elements of the book + * + * @param string $title Title of the book + * @param string $callnumber Callnumber of the book + * + * @return int unique number for this record + */ + protected function createSeed($title, $callnumber) + { + // Turn callnumber into number + if (null == $callnumber) { + $callnumber = $title; + } + if (null !== $callnumber) { + $cv = 0; + for ($i = 0;$i < strlen($callnumber);$i++) { + $cv += ord($callnumber[$i]); + } + return $cv; + } else { + // If no callnumber, random + return ceil(rand(pow(2, 4), pow(2, 32))); + } + } + + /** + * Generate an accent color from a seed value. + * + * @param resource $im Active image resource + * @param int $seed Seed value + * @param object $settings Generator settings object + * + * @return int + */ + protected function getAccentColor($im, $seed, $settings) + { + // Number to color, hsb to control saturation and lightness + return $settings->accentColor == 'random' + ? $this->getHSBColor( + $im, $seed % 256, $settings->saturation, $settings->lightness + ) : $this->getColor($im, $settings->accentColor); + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/AbstractLayer.php b/module/VuFind/src/VuFind/Cover/Layer/AbstractLayer.php new file mode 100644 index 0000000000000000000000000000000000000000..b7f7a8333e13f7bd4ceb145fca043d9b2515996c --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/AbstractLayer.php @@ -0,0 +1,128 @@ +<?php +/** + * Abstract cover layer + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Abstract cover layer + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +abstract class AbstractLayer implements LayerInterface +{ + /** + * Mapping of color names to RGB values. + * + * @var array + */ + protected $colorMap = [ + 'black' => [0, 0, 0], + 'silver' => [192, 192, 192], + 'gray' => [128, 128, 128], + 'white' => [255, 255, 255], + 'maroon' => [128, 0, 0], + 'red' => [255, 0, 0], + 'purple' => [128, 0, 128], + 'fuchsia' => [255, 0, 255], + 'green' => [0, 128, 0], + 'lime' => [0, 255, 0], + 'olive' => [128, 128, 0], + 'yellow' => [255, 255, 0], + 'navy' => [0, 0, 128], + 'blue' => [0, 0, 255], + 'teal' => [0, 128, 128], + 'aqua' => [0, 255, 255], + ]; + + /** + * Check and allocates color + * + * @param resource $im Image resource being updated + * @param string $color Legal color name from HTML4 + * + * @return allocated color + */ + protected function getColor($im, $color) + { + // Case one: named color found in map + $key = strtolower($color); + if (isset($this->colorMap[$key])) { + return imagecolorallocate($im, ...$this->colorMap[$key]); + } + // Case two: hex color + if (substr($color, 0, 1) == '#' && strlen($color) == 7) { + $r = hexdec(substr($color, 1, 2)); + $g = hexdec(substr($color, 3, 2)); + $b = hexdec(substr($color, 5, 2)); + return imagecolorallocate($im, $r, $g, $b); + } + // Default case: unsupported color + return false; + } + + /** + * Using HSB allows us to control the contrast while allowing randomness + * + * @param resource $im Active image resource + * @param int $h Hue (0-255) + * @param int $s Saturation (0-255) + * @param int $v Lightness (0-255) + * + * @return int + */ + protected function getHSBColor($im, $h, $s, $v) + { + $s /= 256.0; + if ($s == 0.0) { + return imagecolorallocate($im, $v, $v, $v); + } + $h /= (256.0 / 6.0); + $i = floor($h); + $f = $h - $i; + $p = (int)($v * (1.0 - $s)); + $q = (int)($v * (1.0 - $s * $f)); + $t = (int)($v * (1.0 - $s * (1.0 - $f))); + switch ($i) { + case 0: + return imagecolorallocate($im, $v, $t, $p); + case 1: + return imagecolorallocate($im, $q, $v, $p); + case 2: + return imagecolorallocate($im, $p, $v, $t); + case 3: + return imagecolorallocate($im, $p, $q, $v); + case 4: + return imagecolorallocate($im, $t, $p, $v); + default: + return imagecolorallocate($im, $v, $p, $q); + } + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/AbstractTextLayer.php b/module/VuFind/src/VuFind/Cover/Layer/AbstractTextLayer.php new file mode 100644 index 0000000000000000000000000000000000000000..1812adbbfd89e23fd6d3d372089e6233d8407f37 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/AbstractTextLayer.php @@ -0,0 +1,121 @@ +<?php +/** + * Abstract cover text layer + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Abstract cover text layer + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +abstract class AbstractTextLayer extends AbstractLayer +{ + /** + * Returns the width a string would render to + * + * @param string $text Text to test + * @param string $font Full font path + * @param string $size Size of the font + * + * @return int + */ + protected function textWidth($text, $font, $size) + { + $p = imagettfbbox($size, 0, $font, $text); + return $p[2] - $p[0]; + } + + /** + * Returns the height a string would render to + * + * @param string $text Text to test + * @param string $font Full font path + * @param string $size Size of the font + * + * @return int + */ + protected function textHeight($text, $font, $size) + { + $p = imagettfbbox($size, 0, $font, $text); + return $p[1] - $p[5]; + } + + /** + * Simulate outlined text + * + * @param resource $im Active image resource + * @param object $settings Generator settings object + * @param string $text Text to render + * @param int $y Top position + * @param string $font Full path to font + * @param int $fontSize Size of the font + * @param int $mcolor Main text color + * @param int $scolor Secondary border color + * @param string $align 'left','center','right' + * + * @return void + */ + protected function drawText($im, $settings, $text, $y, $font, $fontSize, $mcolor, + $scolor = false, $align = null + ) { + // If the wrap width is smaller than the image width, we want to account + // for this when right or left aligning to maintain padding on the image. + $wrapGap = ($settings->width - $settings->wrapWidth) / 2; + + $textWidth = $this->textWidth($text, $font, $fontSize); + if ($textWidth > $settings->width) { + $align = 'left'; + $wrapGap = 0; // kill wrap gap to maximize text fit + } + if (null == $align) { + $align = $settings->textAlign; + } + if ($align == 'left') { + $x = $wrapGap; + } + if ($align == 'center') { + $x = ($settings->width - $textWidth) / 2; + } + if ($align == 'right') { + $x = $settings->width - ($textWidth + $wrapGap); + } + + // Generate 5 lines of text, 4 offset in a border color + if ($scolor) { + imagettftext($im, $fontSize, 0, $x, $y + 1, $scolor, $font, $text); + imagettftext($im, $fontSize, 0, $x, $y - 1, $scolor, $font, $text); + imagettftext($im, $fontSize, 0, $x + 1, $y, $scolor, $font, $text); + imagettftext($im, $fontSize, 0, $x - 1, $y, $scolor, $font, $text); + } + // 1 centered in main color + imagettftext($im, $fontSize, 0, $x, $y, $mcolor, $font, $text); + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/DefaultText.php b/module/VuFind/src/VuFind/Cover/Layer/DefaultText.php new file mode 100644 index 0000000000000000000000000000000000000000..28dd5f78eb43f2ec7bec0fd458022a7b752c85aa --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/DefaultText.php @@ -0,0 +1,180 @@ +<?php +/** + * Default cover text layer + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Default cover text layer + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +class DefaultText extends AbstractTextLayer +{ + /** + * Render the layer + * + * @param resource $im Image resource to draw on + * @param array $details Cover details array (with title/author/call_number) + * @param object $settings Settings object + * + * @return void + */ + public function render($im, $details, $settings) + { + if (null !== $details['title']) { + $lineHeight = $settings->height / 8; + $this->drawTitle($im, $settings, $details['title'], $lineHeight); + } + if (null !== $details['author']) { + $this->drawAuthor($im, $settings, $details['author']); + } + } + + /** + * Render title in wrapped, black text with white border + * + * @param resource $im Image resource to draw on + * @param object $settings Settings object + * @param string $title Title to write + * @param int $lineHeight Pixels we move down each line + * + * @return void + */ + protected function drawTitle($im, $settings, $title, $lineHeight) + { + $titleFillColor = $this->getColor($im, $settings->titleFillColor); + $titleBorderColor = $this->getColor($im, $settings->titleBorderColor); + $words = explode(' ', $title); + // Wrap words into image + // Add words until off image, go back and print + $line = ''; + $lineCount = 0; + $i = 0; + while ($i < count($words) + && $lineCount < $settings->maxTitleLines - 1 + ) { + $pline = $line; + // Format + $text = $words[$i]; + $line .= $text . ' '; + $textWidth = $this->textWidth( + rtrim($line, ' '), + $settings->titleFont, + $settings->titleFontSize + ); + if ($textWidth > $settings->wrapWidth) { + // Print black with white border + $this->drawText( + $im, + $settings, + rtrim($pline, ' '), + $settings->topPadding + $lineHeight * $lineCount, + $settings->titleFont, + $settings->titleFontSize, + $titleFillColor, + $titleBorderColor + ); + $line = $text . ' '; + $lineCount++; + } + $i++; + } + // Print the last words + $this->drawText( + $im, + $settings, + rtrim($line, ' '), + $settings->topPadding + $lineHeight * $lineCount, + $settings->titleFont, + $settings->titleFontSize, + $titleFillColor, + $titleBorderColor + ); + // Add ellipses if we've truncated + if ($i < count($words) - 1) { + $this->drawText( + $im, + $settings, + '...', + $settings->topPadding + $settings->maxTitleLines * $lineHeight, + $settings->titleFont, + $settings->titleFontSize + 1, + $titleFillColor, + $titleBorderColor + ); + } + } + + /** + * Render author at bottom in wrapped, white text with black border + * + * @param resource $im Image resource to draw on + * @param object $settings Settings object + * @param string $author Author to write + * + * @return void + */ + protected function drawAuthor($im, $settings, $author) + { + $authorFillColor = $this->getColor($im, $settings->authorFillColor); + $authorBorderColor = $this->getColor($im, $settings->authorBorderColor); + // Scale author to fit by incrementing fontsizes down + $fontSize = $settings->authorFontSize + 1; + do { + $fontSize--; + $textWidth = $this->textWidth( + $author, + $settings->authorFont, + $fontSize + ); + } while ($textWidth > $settings->wrapWidth && + $fontSize > $settings->minAuthorFontSize + ); + // Too small to read? Align left + $textWidth = $this->textWidth( + $author, + $settings->authorFont, + $fontSize + ); + $align = $textWidth > $settings->width ? 'left' : null; + $this->drawText( + $im, + $settings, + $author, + $settings->height - $settings->bottomPadding, + $settings->authorFont, + $fontSize, + $authorFillColor, + $authorBorderColor, + $align + ); + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/GridBackground.php b/module/VuFind/src/VuFind/Cover/Layer/GridBackground.php new file mode 100644 index 0000000000000000000000000000000000000000..2626a68e9514234e9de5ac44f518ffde6c0ce745 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/GridBackground.php @@ -0,0 +1,131 @@ +<?php +/** + * Grid cover background layer + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Grid cover background layer + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +class GridBackground extends AbstractBackgroundLayer +{ + /** + * Render the layer + * + * @param resource $im Image resource to draw on + * @param array $details Cover details array (with title/author/call_number) + * @param object $settings Settings object + * + * @return void + */ + public function render($im, $details, $settings) + { + // Generate a grid of colors as primary feature + $seed = $this->createSeed($details['title'], $details['callnumber']); + $pattern = $this->createPattern($seed); + $accentColor = $this->getAccentColor($im, $seed, $settings); + $this->renderGrid($im, $pattern, $accentColor, $settings); + } + + /** + * Turn number into pattern + * + * @param int $seed Seed used to generate the pattern + * + * @return string binary string describing a quarter of the pattern + */ + protected function createPattern($seed) + { + // Convert to binary + $bc = decbin($seed); + // If we have less that a half of a quarter + if (strlen($bc) < 8) { + // Rotate square of the first 4 into a 4x2 + // Simulate matrix rotation on string + $bc = substr($bc, 0, 3) + . substr($bc, 0, 1) + . substr($bc, 2, 2) + . substr($bc, 3, 1) + . substr($bc, 1, 1); + } + // If we have less than a quarter + if (strlen($bc) < 16) { + // Rotate the first 8 as a 4x2 into a 4x4 + $bc .= strrev($bc); + } + return $bc; + } + + /** + * Convert 16 long binary string to 8x8 color grid + * Reflects vertically and horizontally + * + * @param resource $im Active image resource + * @param string $pattern Binary string of pattern + * @param int $color Fill color + * @param object $settings Generator settings object + * + * @return void + */ + protected function renderGrid($im, $pattern, $color, $settings) + { + imagefilledrectangle( + $im, 0, 0, $settings->width, $settings->height, + $this->getColor($im, $settings->baseColor) + ); + $halfWidth = $settings->width / 2; + $halfHeight = $settings->height / 2; + $boxWidth = $settings->width / 8; + $boxHeight = $settings->height / 8; + + $bc = str_split($pattern); + for ($k = 0;$k < 4;$k++) { + $x = $k % 2 ? $halfWidth : $halfWidth - $boxWidth; + $y = $k / 2 < 1 ? $halfHeight : $halfHeight - $boxHeight; + $u = $k % 2 ? $boxWidth : -$boxWidth; + $v = $k / 2 < 1 ? $boxHeight : -$boxHeight; + for ($i = 0;$i < 16;$i++) { + if ($bc[$i] == "1") { + imagefilledrectangle( + $im, $x, $y, + $x + $boxWidth - 1, $y + $boxHeight - 1, $color + ); + } + $x += $u; + if ($x >= $settings->width || $x < 0) { + $x = $k % 2 ? $halfWidth : $halfWidth - $boxWidth; + $y += $v; + } + } + } + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/InitialText.php b/module/VuFind/src/VuFind/Cover/Layer/InitialText.php new file mode 100644 index 0000000000000000000000000000000000000000..8519166bc729e9e5d5249dda79cecc6d6fe1d692 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/InitialText.php @@ -0,0 +1,83 @@ +<?php +/** + * Initial cover text layer + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Initial cover text layer + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +class InitialText extends AbstractTextLayer +{ + /** + * Render the layer + * + * @param resource $im Image resource to draw on + * @param array $details Cover details array (with title/author/call_number) + * @param object $settings Settings object + * + * @return void + */ + public function render($im, $details, $settings) + { + // Get the first letter of title or author... + $initial = mb_substr($details['title'] . $details['author'], 0, 1, 'UTF-8'); + + // Get the height of a character with no descenders: + $heightWithoutDescenders + = $this->textHeight('O', $settings->titleFont, $settings->titleFontSize); + + // Get the height of the currently selected character: + $textHeight = $this + ->textHeight($initial, $settings->titleFont, $settings->titleFontSize); + + // Draw the letter... Note that the way we are using $textHeight and + // $heightWithoutDescenders is something of a fudge driven by the fact + // that PHP measures text in total pixels, but positions text using the + // baseline (thus not accounting for descenders). To truly vertically + // center something, we need more information than we can get without + // using an extension or library to read more information from the font + // file. The formula here is not particularly well-informed but seems + // to produce acceptable results for many scenarios. + $this->drawText( + $im, + $settings, + $initial, + $heightWithoutDescenders + ($settings->height - $textHeight) / 2, + $settings->titleFont, + $settings->titleFontSize, + $this->getColor($im, $settings->titleFillColor), + $this->getColor($im, $settings->titleBorderColor), + $settings->textAlign + ); + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/LayerInterface.php b/module/VuFind/src/VuFind/Cover/Layer/LayerInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..faf9e6886349d261477f36382c10d32cd724ce40 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/LayerInterface.php @@ -0,0 +1,51 @@ +<?php +/** + * Cover layer interface + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Cover layer interface + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +interface LayerInterface +{ + /** + * Render the layer + * + * @param resource $im Image resource to draw on + * @param array $details Cover details array (with title/author/call_number) + * @param object $settings Settings object + * + * @return void + */ + public function render($im, $details, $settings); +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/PluginManager.php b/module/VuFind/src/VuFind/Cover/Layer/PluginManager.php new file mode 100644 index 0000000000000000000000000000000000000000..303ec2450e55fb9859f42eb4b898d351dbc72120 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/PluginManager.php @@ -0,0 +1,79 @@ +<?php +/** + * Cover layer plugin manager + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Cover layer plugin manager + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +class PluginManager extends \VuFind\ServiceManager\AbstractPluginManager +{ + /** + * Default plugin aliases. + * + * @var array + */ + protected $aliases = [ + 'defaulttext' => 'VuFind\Cover\Layer\DefaultText', + 'gridbackground' => 'VuFind\Cover\Layer\GridBackground', + 'initialtext' => 'VuFind\Cover\Layer\InitialText', + 'solidbackground' => 'VuFind\Cover\Layer\SolidBackground', + ]; + + /** + * Default plugin factories. + * + * @var array + */ + protected $factories = [ + 'VuFind\Cover\Layer\DefaultText' => + 'Zend\ServiceManager\Factory\InvokableFactory', + 'VuFind\Cover\Layer\GridBackground' => + 'Zend\ServiceManager\Factory\InvokableFactory', + 'VuFind\Cover\Layer\InitialText' => + 'Zend\ServiceManager\Factory\InvokableFactory', + 'VuFind\Cover\Layer\SolidBackground' => + 'Zend\ServiceManager\Factory\InvokableFactory', + ]; + + /** + * Return the name of the base class or interface that plug-ins must conform + * to. + * + * @return string + */ + protected function getExpectedInterface() + { + return 'VuFind\Cover\Layer\LayerInterface'; + } +} diff --git a/module/VuFind/src/VuFind/Cover/Layer/SolidBackground.php b/module/VuFind/src/VuFind/Cover/Layer/SolidBackground.php new file mode 100644 index 0000000000000000000000000000000000000000..2adbc2daa5893f416656e6bbc3e10c001f8f8616 --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/Layer/SolidBackground.php @@ -0,0 +1,59 @@ +<?php +/** + * Solid cover background layer + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +namespace VuFind\Cover\Layer; + +/** + * Solid cover background layer + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:plugins:hierarchy_components Wiki + */ +class SolidBackground extends AbstractBackgroundLayer +{ + /** + * Render the layer + * + * @param resource $im Image resource to draw on + * @param array $details Cover details array (with title/author/call_number) + * @param object $settings Settings object + * + * @return void + */ + public function render($im, $details, $settings) + { + // Fill solid color + $seed = $this->createSeed($details['title'], $details['callnumber']); + $accentColor = $this->getAccentColor($im, $seed, $settings); + imagefilledrectangle( + $im, 0, 0, $settings->width, $settings->height, $accentColor + ); + } +} diff --git a/module/VuFind/src/VuFind/Cover/Loader.php b/module/VuFind/src/VuFind/Cover/Loader.php index 5aa367a42494a2dd9d58aa06d9d0911ecd1522ee..1ca347798e586f35e9f7f9e78df2d2e1c1194443 100644 --- a/module/VuFind/src/VuFind/Cover/Loader.php +++ b/module/VuFind/src/VuFind/Cover/Loader.php @@ -43,6 +43,14 @@ use VuFindCode\ISBN; */ class Loader extends \VuFind\ImageLoader { + /** + * Class for rendering cover images dynamically if no API match found. Omit + * to disable functionality. + * + * @var Generator + */ + protected $generator = null; + /** * Filename constructed from ISBN * @@ -194,19 +202,15 @@ class Loader extends \VuFind\ImageLoader } /** - * Get Cover Generator Object (or return false if disabled) + * Set Cover Generator Object + * + * @param Generator $generator Cover generator * - * @return VuFind\Cover\Generator|bool + * @return void */ - public function getCoverGenerator() + public function setCoverGenerator(Generator $generator) { - if (isset($this->config->Content->makeDynamicCovers) - && $this->config->Content->makeDynamicCovers - ) { - $settings = $this->getCoverGeneratorSettings(); - return new \VuFind\Cover\Generator($this->themeTools, $settings); - } - return false; + $this->generator = $generator; } /** @@ -307,8 +311,9 @@ class Loader extends \VuFind\ImageLoader } elseif (!$this->fetchFromAPI() && !$this->fetchFromContentType() ) { - if ($generator = $this->getCoverGenerator()) { - $this->image = $generator->generate( + if ($this->generator) { + $this->generator->setOptions($this->getCoverGeneratorSettings()); + $this->image = $this->generator->generate( $settings['title'], $settings['author'], $settings['callnumber'] ); $this->contentType = 'image/png'; diff --git a/module/VuFind/src/VuFind/Cover/LoaderFactory.php b/module/VuFind/src/VuFind/Cover/LoaderFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..a8bd85075a701009fb6c453c0ac610de9eb7e95d --- /dev/null +++ b/module/VuFind/src/VuFind/Cover/LoaderFactory.php @@ -0,0 +1,80 @@ +<?php +/** + * Cover loader factory. + * + * PHP version 7 + * + * Copyright (C) Villanova University 2018. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +namespace VuFind\Cover; + +use Interop\Container\ContainerInterface; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Cover loader factory. + * + * @category VuFind + * @package Cover_Generator + * @author Demian Katz <demian.katz@villanova.edu> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class LoaderFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException if any other error occurs + */ + public function __invoke(ContainerInterface $container, $requestedName, + array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + $cacheDir = $container->get('VuFind\Cache\Manager') + ->getCache('cover')->getOptions()->getCacheDir(); + $config = $container->get('VuFind\Config\PluginManager')->get('config'); + $loader = new $requestedName( + $config, + $container->get('VuFind\Content\Covers\PluginManager'), + $container->get('VuFindTheme\ThemeInfo'), + $container->get('VuFindHttp\HttpService'), + $cacheDir + ); + // Add cover generator if enabled: + if ($config->Content->makeDynamicCovers ?? false) { + $loader->setCoverGenerator($container->get('VuFind\Cover\Generator')); + } + return $loader; + } +}