104fd306cSNickeau<?php 204fd306cSNickeau 304fd306cSNickeau 404fd306cSNickeaunamespace ComboStrap; 504fd306cSNickeau 604fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 704fd306cSNickeauuse ComboStrap\Web\Url; 804fd306cSNickeauuse ComboStrap\Xml\XmlDocument; 904fd306cSNickeauuse ComboStrap\Xml\XmlSystems; 1004fd306cSNickeauuse DOMAttr; 1104fd306cSNickeauuse DOMElement; 1204fd306cSNickeauuse splitbrain\phpcli\Colors; 1304fd306cSNickeau 1404fd306cSNickeau/** 1504fd306cSNickeau * Class ImageSvg 1604fd306cSNickeau * @package ComboStrap 1704fd306cSNickeau * 1804fd306cSNickeau * Svg image fetch processing that can output: 1904fd306cSNickeau * * an URL for an HTTP request 2004fd306cSNickeau * * an SvgFile for an HTTP response or any further processing 2104fd306cSNickeau * 2204fd306cSNickeau * The original svg can be set with: 2304fd306cSNickeau * * the {@link FetcherSvg::setSourcePath() original path} 2404fd306cSNickeau * * the {@link FetcherSvg::setRequestedName() name} if this is an {@link FetcherSvg::setRequestedType() icon type}, the original path is then determined on {@link FetcherSvg::getSourcePath() get} 2504fd306cSNickeau * * or by {@link FetcherSvg::setMarkup() Svg Markup} 2604fd306cSNickeau * 2704fd306cSNickeau */ 2804fd306cSNickeauclass FetcherSvg extends IFetcherLocalImage 2904fd306cSNickeau{ 3004fd306cSNickeau 3104fd306cSNickeau use FetcherTraitWikiPath { 3204fd306cSNickeau setSourcePath as protected setOriginalPathTraitAlias; 3304fd306cSNickeau } 3404fd306cSNickeau 3504fd306cSNickeau const EXTENSION = "svg"; 3604fd306cSNickeau const CANONICAL = "svg"; 3704fd306cSNickeau 3804fd306cSNickeau const REQUESTED_PRESERVE_ASPECT_RATIO_KEY = "preserveAspectRatio"; 3904fd306cSNickeau public const CURRENT_COLOR = "currentColor"; 4004fd306cSNickeau /** 4104fd306cSNickeau * Default SVG values 4204fd306cSNickeau * https://github.com/svg/svgo/blob/master/plugins/_collections.js#L1579 4304fd306cSNickeau * The key are exact (not lowercase) to be able to look them up 4404fd306cSNickeau * for optimization 4504fd306cSNickeau */ 4604fd306cSNickeau public const SVG_DEFAULT_ATTRIBUTES_VALUE = array( 4704fd306cSNickeau "x" => '0', 4804fd306cSNickeau "y" => '0', 4904fd306cSNickeau "width" => '100%', 5004fd306cSNickeau "height" => '100%', 5104fd306cSNickeau "preserveAspectRatio" => 'xMidYMid meet', 5204fd306cSNickeau "zoomAndPan" => 'magnify', 5304fd306cSNickeau "version" => '1.1', 5404fd306cSNickeau "baseProfile" => 'none', 5504fd306cSNickeau "contentScriptType" => 'application/ecmascript', 5604fd306cSNickeau "contentStyleType" => 'text/css', 5704fd306cSNickeau ); 5804fd306cSNickeau /** 5904fd306cSNickeau * The namespace of the editors 6004fd306cSNickeau * https://github.com/svg/svgo/blob/master/plugins/_collections.js#L1841 6104fd306cSNickeau */ 6204fd306cSNickeau public const EDITOR_NAMESPACE = [ 6304fd306cSNickeau 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', 6404fd306cSNickeau 'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd', 6504fd306cSNickeau 'http://www.inkscape.org/namespaces/inkscape', 6604fd306cSNickeau 'http://www.bohemiancoding.com/sketch/ns', 6704fd306cSNickeau 'http://ns.adobe.com/AdobeIllustrator/10.0/', 6804fd306cSNickeau 'http://ns.adobe.com/Graphs/1.0/', 6904fd306cSNickeau 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/', 7004fd306cSNickeau 'http://ns.adobe.com/Variables/1.0/', 7104fd306cSNickeau 'http://ns.adobe.com/SaveForWeb/1.0/', 7204fd306cSNickeau 'http://ns.adobe.com/Extensibility/1.0/', 7304fd306cSNickeau 'http://ns.adobe.com/Flows/1.0/', 7404fd306cSNickeau 'http://ns.adobe.com/ImageReplacement/1.0/', 7504fd306cSNickeau 'http://ns.adobe.com/GenericCustomNamespace/1.0/', 7604fd306cSNickeau 'http://ns.adobe.com/XPath/1.0/', 7704fd306cSNickeau 'http://schemas.microsoft.com/visio/2003/SVGExtensions/', 7804fd306cSNickeau 'http://taptrix.com/vectorillustrator/svg_extensions', 7904fd306cSNickeau 'http://www.figma.com/figma/ns', 8004fd306cSNickeau 'http://purl.org/dc/elements/1.1/', 8104fd306cSNickeau 'http://creativecommons.org/ns#', 8204fd306cSNickeau 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 8304fd306cSNickeau 'http://www.serif.com/', 8404fd306cSNickeau 'http://www.vector.evaxdesign.sk', 8504fd306cSNickeau ]; 8604fd306cSNickeau public const CONF_PRESERVE_ASPECT_RATIO_DEFAULT = "svgPreserveAspectRatioDefault"; 8704fd306cSNickeau public const TILE_TYPE = "tile"; 8804fd306cSNickeau public const CONF_OPTIMIZATION_ELEMENTS_TO_DELETE = "svgOptimizationElementsToDelete"; 8904fd306cSNickeau public const VIEW_BOX = "viewBox"; 9004fd306cSNickeau /** 9104fd306cSNickeau * Optimization Configuration 9204fd306cSNickeau */ 9304fd306cSNickeau public const CONF_OPTIMIZATION_NAMESPACES_TO_KEEP = "svgOptimizationNamespacesToKeep"; 9404fd306cSNickeau public const CONF_SVG_OPTIMIZATION_ENABLE = "svgOptimizationEnable"; 9504fd306cSNickeau public const COLOR_TYPE_STROKE_OUTLINE = FetcherSvg::STROKE_ATTRIBUTE; 9604fd306cSNickeau public const CONF_OPTIMIZATION_ATTRIBUTES_TO_DELETE = "svgOptimizationAttributesToDelete"; 9704fd306cSNickeau public const CONF_OPTIMIZATION_ELEMENTS_TO_DELETE_IF_EMPTY = "svgOptimizationElementsToDeleteIfEmpty"; 9804fd306cSNickeau public const SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg"; 9904fd306cSNickeau public const STROKE_ATTRIBUTE = "stroke"; 10004fd306cSNickeau public const DEFAULT_ICON_LENGTH = 24; 10104fd306cSNickeau public const REQUESTED_NAME_ATTRIBUTE = "name"; 10204fd306cSNickeau public const REQUESTED_PRESERVE_ATTRIBUTE = "preserve"; 10304fd306cSNickeau public const ILLUSTRATION_TYPE = "illustration"; 10404fd306cSNickeau /** 10504fd306cSNickeau * There is only two type of svg icon / tile 10604fd306cSNickeau * * fill color is on the surface (known also as Solid) 10704fd306cSNickeau * * stroke, the color is on the path (known as Outline 10804fd306cSNickeau */ 10904fd306cSNickeau public const COLOR_TYPE_FILL_SOLID = "fill"; 11004fd306cSNickeau /** 11104fd306cSNickeau * Type of svg 11204fd306cSNickeau * * Icon and tile have the same characteristic (ie viewbox = 0 0 A A) and the color can be set) 11304fd306cSNickeau * * An illustration does not have rectangle shape and the color is not set 11404fd306cSNickeau */ 11504fd306cSNickeau public const ICON_TYPE = "icon"; 11604fd306cSNickeau /** 11704fd306cSNickeau * Namespace (used to query with xpath only the svg node) 11804fd306cSNickeau */ 11904fd306cSNickeau public const SVG_NAMESPACE_PREFIX = "svg"; 12004fd306cSNickeau const TAG = "svg"; 12104fd306cSNickeau public const NAME_ATTRIBUTE = "name"; 12204fd306cSNickeau public const DATA_NAME_HTML_ATTRIBUTE = "data-name"; 12304fd306cSNickeau const DEFAULT_TILE_WIDTH = 192; 12404fd306cSNickeau 12504fd306cSNickeau 12604fd306cSNickeau private ?ColorRgb $color = null; 12704fd306cSNickeau private ?string $preserveAspectRatio = null; 12804fd306cSNickeau private ?bool $preserveStyle = null; 12904fd306cSNickeau private ?string $requestedType = null; 13004fd306cSNickeau private bool $processed = false; 13104fd306cSNickeau private ?float $zoomFactor = null; 13204fd306cSNickeau private ?string $requestedClass = null; 13304fd306cSNickeau private int $intrinsicHeight; 13404fd306cSNickeau private int $intrinsicWidth; 13504fd306cSNickeau private string $name; 13604fd306cSNickeau 13704fd306cSNickeau 13804fd306cSNickeau private static function createSvgEmpty(): FetcherSvg 13904fd306cSNickeau { 14004fd306cSNickeau return new FetcherSvg(); 14104fd306cSNickeau } 14204fd306cSNickeau 14304fd306cSNickeau /** 14404fd306cSNickeau */ 14504fd306cSNickeau public static function createSvgFromPath(WikiPath $path): FetcherSvg 14604fd306cSNickeau { 14704fd306cSNickeau $fetcher = self::createSvgEmpty(); 14804fd306cSNickeau 14904fd306cSNickeau $fetcher->setSourcePath($path); 15004fd306cSNickeau return $fetcher; 15104fd306cSNickeau } 15204fd306cSNickeau 15304fd306cSNickeau /** 15404fd306cSNickeau * @throws ExceptionBadArgument 15504fd306cSNickeau */ 15604fd306cSNickeau public static function createSvgFromFetchUrl(Url $fetchUrl): FetcherSvg 15704fd306cSNickeau { 15804fd306cSNickeau $fetchSvg = self::createSvgEmpty(); 15904fd306cSNickeau $fetchSvg->buildFromUrl($fetchUrl); 16004fd306cSNickeau return $fetchSvg; 16104fd306cSNickeau } 16204fd306cSNickeau 16304fd306cSNickeau /** 16404fd306cSNickeau * @param string $markup - the svg as a string 16504fd306cSNickeau * @param string $name - a name identifier (used in diff) 16604fd306cSNickeau * @return FetcherSvg 16704fd306cSNickeau * @throws ExceptionBadSyntax 16804fd306cSNickeau */ 16904fd306cSNickeau public static function createSvgFromMarkup(string $markup, string $name): FetcherSvg 17004fd306cSNickeau { 17104fd306cSNickeau return self::createSvgEmpty()->setMarkup($markup, $name); 17204fd306cSNickeau } 17304fd306cSNickeau 17404fd306cSNickeau /** 17504fd306cSNickeau * @param TagAttributes $tagAttributes 17604fd306cSNickeau * @return FetcherSvg 17704fd306cSNickeau * @throws ExceptionBadArgument 17804fd306cSNickeau * @throws ExceptionBadSyntax 17904fd306cSNickeau * @throws ExceptionCompile 18004fd306cSNickeau */ 18104fd306cSNickeau public static function createFromAttributes(TagAttributes $tagAttributes): FetcherSvg 18204fd306cSNickeau { 18304fd306cSNickeau $fetcher = FetcherSvg::createSvgEmpty(); 18404fd306cSNickeau $fetcher->buildFromTagAttributes($tagAttributes); 18504fd306cSNickeau return $fetcher; 18604fd306cSNickeau } 18704fd306cSNickeau 18804fd306cSNickeau /** 18904fd306cSNickeau * @throws ExceptionNotFound 19004fd306cSNickeau */ 19104fd306cSNickeau public function getRequestedOptimization(): bool 19204fd306cSNickeau { 19304fd306cSNickeau 19404fd306cSNickeau if ($this->requestedOptimization === null) { 19504fd306cSNickeau throw new ExceptionNotFound("Optimization was not set"); 19604fd306cSNickeau } 19704fd306cSNickeau return $this->requestedOptimization; 19804fd306cSNickeau 19904fd306cSNickeau } 20004fd306cSNickeau 20104fd306cSNickeau public function getRequestedOptimizeOrDefault(): bool 20204fd306cSNickeau { 20304fd306cSNickeau try { 20404fd306cSNickeau return $this->getRequestedOptimization(); 20504fd306cSNickeau } catch (ExceptionNotFound $e) { 20604fd306cSNickeau return SiteConfig::getConfValue(FetcherSvg::CONF_SVG_OPTIMIZATION_ENABLE, 1); 20704fd306cSNickeau } 20804fd306cSNickeau 20904fd306cSNickeau } 21004fd306cSNickeau 21104fd306cSNickeau /** 21204fd306cSNickeau * @throws ExceptionNotFound 21304fd306cSNickeau */ 21404fd306cSNickeau public function getRequestedPreserveStyle(): bool 21504fd306cSNickeau { 21604fd306cSNickeau 21704fd306cSNickeau if ($this->preserveStyle === null) { 21804fd306cSNickeau throw new ExceptionNotFound("No preserve style attribute was set"); 21904fd306cSNickeau } 22004fd306cSNickeau return $this->preserveStyle; 22104fd306cSNickeau 22204fd306cSNickeau } 22304fd306cSNickeau 22404fd306cSNickeau 22504fd306cSNickeau /** 22604fd306cSNickeau * @param $boolean 22704fd306cSNickeau * @return FetcherSvg 22804fd306cSNickeau */ 22904fd306cSNickeau public function setRequestedOptimization($boolean): FetcherSvg 23004fd306cSNickeau { 23104fd306cSNickeau $this->requestedOptimization = $boolean; 23204fd306cSNickeau return $this; 23304fd306cSNickeau } 23404fd306cSNickeau 23504fd306cSNickeau /** 23604fd306cSNickeau * Optimization 23704fd306cSNickeau * Based on https://jakearchibald.github.io/svgomg/ 23804fd306cSNickeau * (gui of https://github.com/svg/svgo) 23904fd306cSNickeau * 24004fd306cSNickeau * @throws ExceptionBadSyntax 24104fd306cSNickeau * @throws ExceptionBadState 24204fd306cSNickeau */ 24304fd306cSNickeau public 24404fd306cSNickeau function optimize() 24504fd306cSNickeau { 24604fd306cSNickeau 24704fd306cSNickeau if ($this->getRequestedOptimizeOrDefault()) { 24804fd306cSNickeau 24904fd306cSNickeau /** 25004fd306cSNickeau * Delete Editor namespace 25104fd306cSNickeau * https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js 25204fd306cSNickeau */ 25304fd306cSNickeau $confNamespaceToKeeps = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_NAMESPACES_TO_KEEP); 25404fd306cSNickeau $namespaceToKeep = StringUtility::explodeAndTrim($confNamespaceToKeeps, ","); 25504fd306cSNickeau foreach ($this->getXmlDocument()->getNamespaces() as $namespacePrefix => $namespaceUri) { 25604fd306cSNickeau if ( 25704fd306cSNickeau !empty($namespacePrefix) 25804fd306cSNickeau && $namespacePrefix != "svg" 25904fd306cSNickeau && !in_array($namespacePrefix, $namespaceToKeep) 26004fd306cSNickeau && in_array($namespaceUri, FetcherSvg::EDITOR_NAMESPACE) 26104fd306cSNickeau ) { 26204fd306cSNickeau $this->getXmlDocument()->removeNamespace($namespaceUri); 26304fd306cSNickeau } 26404fd306cSNickeau } 26504fd306cSNickeau 26604fd306cSNickeau /** 26704fd306cSNickeau * Delete empty namespace rules 26804fd306cSNickeau */ 26904fd306cSNickeau $documentElement = $this->getXmlDocument()->getDomDocument()->documentElement; 27004fd306cSNickeau foreach ($this->getXmlDocument()->getNamespaces() as $namespacePrefix => $namespaceUri) { 27104fd306cSNickeau $nodes = $this->getXmlDocument()->xpath("//*[namespace-uri()='$namespaceUri']"); 27204fd306cSNickeau $attributes = $this->getXmlDocument()->xpath("//@*[namespace-uri()='$namespaceUri']"); 27304fd306cSNickeau if ($nodes->length == 0 && $attributes->length == 0) { 27404fd306cSNickeau $result = $documentElement->removeAttributeNS($namespaceUri, $namespacePrefix); 27504fd306cSNickeau if ($result === false) { 27604fd306cSNickeau LogUtility::msg("Internal error: The deletion of the empty namespace ($namespacePrefix:$namespaceUri) didn't succeed", LogUtility::LVL_MSG_WARNING, "support"); 27704fd306cSNickeau } 27804fd306cSNickeau } 27904fd306cSNickeau } 28004fd306cSNickeau 28104fd306cSNickeau /** 28204fd306cSNickeau * Delete comments 28304fd306cSNickeau */ 28404fd306cSNickeau $commentNodes = $this->getXmlDocument()->xpath("//comment()"); 28504fd306cSNickeau foreach ($commentNodes as $commentNode) { 28604fd306cSNickeau $this->getXmlDocument()->removeNode($commentNode); 28704fd306cSNickeau } 28804fd306cSNickeau 28904fd306cSNickeau /** 29004fd306cSNickeau * Delete default value (version=1.1 for instance) 29104fd306cSNickeau */ 29204fd306cSNickeau $defaultValues = FetcherSvg::SVG_DEFAULT_ATTRIBUTES_VALUE; 29304fd306cSNickeau foreach ($documentElement->attributes as $attribute) { 29404fd306cSNickeau /** @var DOMAttr $attribute */ 29504fd306cSNickeau $name = $attribute->name; 29604fd306cSNickeau if (isset($defaultValues[$name])) { 29704fd306cSNickeau if ($defaultValues[$name] == $attribute->value) { 29804fd306cSNickeau $documentElement->removeAttributeNode($attribute); 29904fd306cSNickeau } 30004fd306cSNickeau } 30104fd306cSNickeau } 30204fd306cSNickeau 30304fd306cSNickeau /** 30404fd306cSNickeau * Suppress the attributes (by default id, style and class, data-name) 30504fd306cSNickeau */ 30604fd306cSNickeau $attributeConfToDelete = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_ATTRIBUTES_TO_DELETE, "id, style, class, data-name"); 30704fd306cSNickeau $attributesNameToDelete = StringUtility::explodeAndTrim($attributeConfToDelete, ","); 30804fd306cSNickeau foreach ($attributesNameToDelete as $value) { 30904fd306cSNickeau 31004fd306cSNickeau if (in_array($value, ["style", "class", "id"]) && $this->getRequestedPreserveStyleOrDefault()) { 31104fd306cSNickeau // we preserve the style, we preserve the class 31204fd306cSNickeau continue; 31304fd306cSNickeau } 31404fd306cSNickeau 31504fd306cSNickeau $nodes = $this->getXmlDocument()->xpath("//@$value"); 31604fd306cSNickeau foreach ($nodes as $node) { 31704fd306cSNickeau /** @var DOMAttr $node */ 31804fd306cSNickeau /** @var DOMElement $DOMNode */ 31904fd306cSNickeau $DOMNode = $node->parentNode; 32004fd306cSNickeau $DOMNode->removeAttributeNode($node); 32104fd306cSNickeau } 32204fd306cSNickeau } 32304fd306cSNickeau 32404fd306cSNickeau /** 32504fd306cSNickeau * Remove width/height that coincides with a viewBox attr 32604fd306cSNickeau * https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute 32704fd306cSNickeau * Example: 32804fd306cSNickeau * <svg width="100" height="50" viewBox="0 0 100 50"> 32904fd306cSNickeau * <svg viewBox="0 0 100 50"> 33004fd306cSNickeau * 33104fd306cSNickeau */ 33204fd306cSNickeau $widthAttributeValue = $documentElement->getAttribute("width"); 33304fd306cSNickeau if (!empty($widthAttributeValue)) { 33404fd306cSNickeau $widthPixel = Unit::toPixel($widthAttributeValue); 33504fd306cSNickeau 33604fd306cSNickeau $heightAttributeValue = $documentElement->getAttribute("height"); 33704fd306cSNickeau if (!empty($heightAttributeValue)) { 33804fd306cSNickeau $heightPixel = Unit::toPixel($heightAttributeValue); 33904fd306cSNickeau 34004fd306cSNickeau // ViewBox 34104fd306cSNickeau $viewBoxAttribute = $documentElement->getAttribute(FetcherSvg::VIEW_BOX); 34204fd306cSNickeau if (!empty($viewBoxAttribute)) { 34304fd306cSNickeau $viewBoxAttributeAsArray = StringUtility::explodeAndTrim($viewBoxAttribute, " "); 34404fd306cSNickeau 34504fd306cSNickeau if (sizeof($viewBoxAttributeAsArray) == 4) { 34604fd306cSNickeau $minX = $viewBoxAttributeAsArray[0]; 34704fd306cSNickeau $minY = $viewBoxAttributeAsArray[1]; 34804fd306cSNickeau $widthViewPort = $viewBoxAttributeAsArray[2]; 34904fd306cSNickeau $heightViewPort = $viewBoxAttributeAsArray[3]; 35004fd306cSNickeau if ( 35104fd306cSNickeau $minX == 0 & 35204fd306cSNickeau $minY == 0 & 35304fd306cSNickeau $widthViewPort == $widthPixel & 35404fd306cSNickeau $heightViewPort == $heightPixel 35504fd306cSNickeau ) { 35604fd306cSNickeau $documentElement->removeAttribute("width"); 35704fd306cSNickeau $documentElement->removeAttribute("height"); 35804fd306cSNickeau } 35904fd306cSNickeau 36004fd306cSNickeau } 36104fd306cSNickeau } 36204fd306cSNickeau } 36304fd306cSNickeau } 36404fd306cSNickeau 36504fd306cSNickeau 36604fd306cSNickeau /** 36704fd306cSNickeau * Suppress script and style 36804fd306cSNickeau * 36904fd306cSNickeau * 37004fd306cSNickeau * Delete of scripts https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script 37104fd306cSNickeau * 37204fd306cSNickeau * And defs/style 37304fd306cSNickeau * 37404fd306cSNickeau * The style can leak in other icon/svg inlined in the document 37504fd306cSNickeau * 37604fd306cSNickeau * Technically on icon, there should be no `style` 37704fd306cSNickeau * on inline icon otherwise, the css style can leak 37804fd306cSNickeau * 37904fd306cSNickeau * Example with carbon that use cls-1 on all icons 38004fd306cSNickeau * https://github.com/carbon-design-system/carbon/issues/5568 38104fd306cSNickeau * The facebook icon has a class cls-1 with an opacity of 0 38204fd306cSNickeau * that leaks to the tumblr icon that has also a cls-1 class 38304fd306cSNickeau * 38404fd306cSNickeau * The illustration uses inline fill to color and styled 38504fd306cSNickeau * For instance, all un-draw: https://undraw.co/illustrations 38604fd306cSNickeau */ 38704fd306cSNickeau $elementsToDeleteConf = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_ELEMENTS_TO_DELETE, "script, style, title, desc"); 38804fd306cSNickeau $elementsToDelete = StringUtility::explodeAndTrim($elementsToDeleteConf, ","); 38904fd306cSNickeau foreach ($elementsToDelete as $elementToDelete) { 39004fd306cSNickeau if ($elementToDelete === "style" && $this->getRequestedPreserveStyleOrDefault()) { 39104fd306cSNickeau continue; 39204fd306cSNickeau } 39304fd306cSNickeau XmlSystems::deleteAllElementsByName($elementToDelete, $this->getXmlDocument()); 39404fd306cSNickeau } 39504fd306cSNickeau 39604fd306cSNickeau // Delete If Empty 39704fd306cSNickeau // * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs 39804fd306cSNickeau // * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/metadata 39904fd306cSNickeau $elementsToDeleteIfEmptyConf = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_ELEMENTS_TO_DELETE_IF_EMPTY, "metadata, defs, g"); 40004fd306cSNickeau $elementsToDeleteIfEmpty = StringUtility::explodeAndTrim($elementsToDeleteIfEmptyConf); 40104fd306cSNickeau foreach ($elementsToDeleteIfEmpty as $elementToDeleteIfEmpty) { 40204fd306cSNickeau $elementNodeList = $this->getXmlDocument()->xpath("//*[local-name()='$elementToDeleteIfEmpty']"); 40304fd306cSNickeau foreach ($elementNodeList as $element) { 40404fd306cSNickeau /** @var DOMElement $element */ 40504fd306cSNickeau if (!$element->hasChildNodes()) { 40604fd306cSNickeau $element->parentNode->removeChild($element); 40704fd306cSNickeau } 40804fd306cSNickeau } 40904fd306cSNickeau } 41004fd306cSNickeau 41104fd306cSNickeau /** 41204fd306cSNickeau * Delete the svg prefix namespace definition 41304fd306cSNickeau * At the end to be able to query with svg as prefix 41404fd306cSNickeau */ 41504fd306cSNickeau if (!in_array("svg", $namespaceToKeep)) { 41604fd306cSNickeau $documentElement->removeAttributeNS(FetcherSvg::SVG_NAMESPACE_URI, FetcherSvg::SVG_NAMESPACE_PREFIX); 41704fd306cSNickeau } 41804fd306cSNickeau 41904fd306cSNickeau } 42004fd306cSNickeau } 42104fd306cSNickeau 42204fd306cSNickeau 42304fd306cSNickeau /** 42404fd306cSNickeau * The height of the viewbox 42504fd306cSNickeau * @return int 42604fd306cSNickeau */ 42704fd306cSNickeau public function getIntrinsicHeight(): int 42804fd306cSNickeau { 42904fd306cSNickeau 43004fd306cSNickeau try { 43104fd306cSNickeau $this->buildXmlDocumentIfNeeded(); 43204fd306cSNickeau } catch (ExceptionBadSyntax $e) { 43304fd306cSNickeau throw new ExceptionBadSyntaxRuntime($e->getMessage(), self::CANONICAL, 1, $e); 43404fd306cSNickeau } 43504fd306cSNickeau return $this->intrinsicHeight; 43604fd306cSNickeau 43704fd306cSNickeau } 43804fd306cSNickeau 43904fd306cSNickeau /** 44004fd306cSNickeau * The width of the view box 44104fd306cSNickeau * @return int 44204fd306cSNickeau */ 44304fd306cSNickeau public 44404fd306cSNickeau function getIntrinsicWidth(): int 44504fd306cSNickeau { 44604fd306cSNickeau try { 44704fd306cSNickeau $this->buildXmlDocumentIfNeeded(); 44804fd306cSNickeau } catch (ExceptionBadSyntax $e) { 44904fd306cSNickeau throw new ExceptionBadSyntaxRuntime($e->getMessage(), self::CANONICAL, 1, $e); 45004fd306cSNickeau } 45104fd306cSNickeau return $this->intrinsicWidth; 45204fd306cSNickeau } 45304fd306cSNickeau 45404fd306cSNickeau /** 45504fd306cSNickeau * @return string 45604fd306cSNickeau * @throws ExceptionBadArgument 45704fd306cSNickeau * @throws ExceptionBadState 45804fd306cSNickeau * @throws ExceptionBadSyntax 45904fd306cSNickeau * @throws ExceptionCompile 46004fd306cSNickeau * @throws ExceptionNotFound 46104fd306cSNickeau */ 46204fd306cSNickeau public function processAndGetMarkup(): string 46304fd306cSNickeau { 46404fd306cSNickeau 46504fd306cSNickeau return $this->process()->getMarkup(); 46604fd306cSNickeau 46704fd306cSNickeau 46804fd306cSNickeau } 46904fd306cSNickeau 47004fd306cSNickeau 47104fd306cSNickeau /** 47204fd306cSNickeau * @throws ExceptionBadState - if no svg was set to be processed 47304fd306cSNickeau */ 47404fd306cSNickeau public function getMarkup(): string 47504fd306cSNickeau { 47604fd306cSNickeau return $this->getXmlDocument()->toXml(); 47704fd306cSNickeau } 47804fd306cSNickeau 47904fd306cSNickeau /** 48004fd306cSNickeau * @throws ExceptionBadSyntax 48104fd306cSNickeau * @throws ExceptionNotFound 48204fd306cSNickeau */ 48304fd306cSNickeau public function setSourcePath(WikiPath $path): IFetcherLocalImage 48404fd306cSNickeau { 48504fd306cSNickeau 48604fd306cSNickeau $this->setOriginalPathTraitAlias($path); 48704fd306cSNickeau return $this; 48804fd306cSNickeau 48904fd306cSNickeau } 49004fd306cSNickeau 49104fd306cSNickeau 49204fd306cSNickeau /** 49304fd306cSNickeau * 49404fd306cSNickeau * @return Url - the fetch url 49504fd306cSNickeau * 49604fd306cSNickeau */ 49704fd306cSNickeau public function getFetchUrl(Url $url = null): Url 49804fd306cSNickeau { 49904fd306cSNickeau 50004fd306cSNickeau $url = parent::getFetchUrl($url); 50104fd306cSNickeau 50204fd306cSNickeau /** 50304fd306cSNickeau * Trait 50404fd306cSNickeau */ 505*70bbd7f1Sgerardnico $this->addLocalPathParametersToFetchUrl($url, MediaMarkup::$MEDIA_QUERY_PARAMETER); 50604fd306cSNickeau 50704fd306cSNickeau /** 50804fd306cSNickeau * Specific properties 50904fd306cSNickeau */ 51004fd306cSNickeau try { 51104fd306cSNickeau $url->addQueryParameter(ColorRgb::COLOR, $this->getRequestedColor()->toCssValue()); 51204fd306cSNickeau } catch (ExceptionNotFound $e) { 51304fd306cSNickeau // no color ok 51404fd306cSNickeau } 51504fd306cSNickeau try { 51604fd306cSNickeau $url->addQueryParameter(self::REQUESTED_PRESERVE_ASPECT_RATIO_KEY, $this->getRequestedPreserveAspectRatio()); 51704fd306cSNickeau } catch (ExceptionNotFound $e) { 51804fd306cSNickeau // no preserve ratio ok 51904fd306cSNickeau } 52004fd306cSNickeau try { 52104fd306cSNickeau $url->addQueryParameter(self::REQUESTED_NAME_ATTRIBUTE, $this->getRequestedName()); 52204fd306cSNickeau } catch (ExceptionNotFound $e) { 52304fd306cSNickeau // no name 52404fd306cSNickeau } 52504fd306cSNickeau try { 52604fd306cSNickeau $url->addQueryParameter(Dimension::ZOOM_ATTRIBUTE, $this->getRequestedZoom()); 52704fd306cSNickeau } catch (ExceptionNotFound $e) { 52804fd306cSNickeau // no name 52904fd306cSNickeau } 53004fd306cSNickeau try { 53104fd306cSNickeau $url->addQueryParameter(TagAttributes::CLASS_KEY, $this->getRequestedClass()); 53204fd306cSNickeau } catch (ExceptionNotFound $e) { 53304fd306cSNickeau // no name 53404fd306cSNickeau } 53504fd306cSNickeau try { 53604fd306cSNickeau $url->addQueryParameter(TagAttributes::TYPE_KEY, $this->getRequestedType()); 53704fd306cSNickeau } catch (ExceptionNotFound $e) { 53804fd306cSNickeau // no name 53904fd306cSNickeau } 54004fd306cSNickeau 54104fd306cSNickeau return $url; 54204fd306cSNickeau 54304fd306cSNickeau } 54404fd306cSNickeau 54504fd306cSNickeau /** 54604fd306cSNickeau * @throws ExceptionNotFound 54704fd306cSNickeau */ 54804fd306cSNickeau public function getRequestedPreserveAspectRatio(): string 54904fd306cSNickeau { 55004fd306cSNickeau if ($this->preserveAspectRatio === null) { 55104fd306cSNickeau throw new ExceptionNotFound("No preserve Aspect Ratio was requested"); 55204fd306cSNickeau } 55304fd306cSNickeau return $this->preserveAspectRatio; 55404fd306cSNickeau } 55504fd306cSNickeau 55604fd306cSNickeau /** 55704fd306cSNickeau * Return the svg file transformed by the attributes 55804fd306cSNickeau * from cache if possible. Used when making a fetch with the URL 55904fd306cSNickeau * @return LocalPath 56004fd306cSNickeau * @throws ExceptionBadArgument 56104fd306cSNickeau * @throws ExceptionBadState 56204fd306cSNickeau * @throws ExceptionBadSyntax - the file is not a svg file 56304fd306cSNickeau * @throws ExceptionCompile 56404fd306cSNickeau * @throws ExceptionNotFound - the file was not found 56504fd306cSNickeau */ 56604fd306cSNickeau public function getFetchPath(): LocalPath 56704fd306cSNickeau { 56804fd306cSNickeau 56904fd306cSNickeau /** 57004fd306cSNickeau * Generated svg file cache init 57104fd306cSNickeau */ 57204fd306cSNickeau $fetchCache = FetcherCache::createFrom($this); 57304fd306cSNickeau $files[] = $this->getSourcePath(); 57404fd306cSNickeau try { 57504fd306cSNickeau $files[] = ClassUtility::getClassPath(FetcherSvg::class); 57604fd306cSNickeau } catch (\ReflectionException $e) { 57704fd306cSNickeau LogUtility::internalError("Unable to add the FetchImageSvg class as dependency. Error: {$e->getMessage()}"); 57804fd306cSNickeau } 57904fd306cSNickeau try { 58004fd306cSNickeau $files[] = ClassUtility::getClassPath(XmlDocument::class); 58104fd306cSNickeau } catch (\ReflectionException $e) { 58204fd306cSNickeau LogUtility::internalError("Unable to add the XmlDocument class as dependency. Error: {$e->getMessage()}"); 58304fd306cSNickeau } 58404fd306cSNickeau $files = array_merge(Site::getConfigurationFiles(), $files); // svg generation depends on configuration 58504fd306cSNickeau foreach ($files as $file) { 58604fd306cSNickeau $fetchCache->addFileDependency($file); 58704fd306cSNickeau } 58804fd306cSNickeau 58904fd306cSNickeau global $ACT; 59004fd306cSNickeau if (PluginUtility::isDev() && $ACT === ExecutionContext::PREVIEW_ACTION) { 59104fd306cSNickeau // in dev mode, don't cache 59204fd306cSNickeau $isCacheUsable = false; 59304fd306cSNickeau } else { 59404fd306cSNickeau $isCacheUsable = $fetchCache->isCacheUsable(); 59504fd306cSNickeau } 59604fd306cSNickeau if (!$isCacheUsable) { 59704fd306cSNickeau $content = self::processAndGetMarkup(); 59804fd306cSNickeau $fetchCache->storeCache($content); 59904fd306cSNickeau } 60004fd306cSNickeau return $fetchCache->getFile(); 60104fd306cSNickeau 60204fd306cSNickeau } 60304fd306cSNickeau 60404fd306cSNickeau /** 60504fd306cSNickeau * The buster is also based on the configuration file 60604fd306cSNickeau * 60704fd306cSNickeau * It the user changes the configuration, the svg file is generated 60804fd306cSNickeau * again and the browser cache should be deleted (ie the buster regenerated) 60904fd306cSNickeau * 61004fd306cSNickeau * {@link ResourceCombo::getBuster()} 61104fd306cSNickeau * @return string 61204fd306cSNickeau * 61304fd306cSNickeau * @throws ExceptionNotFound 61404fd306cSNickeau */ 61504fd306cSNickeau public function getBuster(): string 61604fd306cSNickeau { 61704fd306cSNickeau $buster = FileSystems::getCacheBuster($this->getSourcePath()); 61804fd306cSNickeau try { 61904fd306cSNickeau $configFile = FileSystems::getCacheBuster(DirectoryLayout::getConfLocalFilePath()); 62004fd306cSNickeau $buster = "$buster-$configFile"; 62104fd306cSNickeau } catch (ExceptionNotFound $e) { 62204fd306cSNickeau // no local conf file 62304fd306cSNickeau if (PluginUtility::isDevOrTest()) { 62404fd306cSNickeau LogUtility::internalError("A local configuration file should be present in dev"); 62504fd306cSNickeau } 62604fd306cSNickeau } 62704fd306cSNickeau return $buster; 62804fd306cSNickeau 62904fd306cSNickeau } 63004fd306cSNickeau 63104fd306cSNickeau 63204fd306cSNickeau function acceptsFetchUrl(Url $url): bool 63304fd306cSNickeau { 63404fd306cSNickeau 63504fd306cSNickeau try { 63604fd306cSNickeau $dokuPath = FetcherRawLocalPath::createEmpty()->buildFromUrl($url)->processIfNeededAndGetFetchPath(); 63704fd306cSNickeau } catch (ExceptionBadArgument $e) { 63804fd306cSNickeau return false; 63904fd306cSNickeau } 64004fd306cSNickeau try { 64104fd306cSNickeau $mime = FileSystems::getMime($dokuPath); 64204fd306cSNickeau } catch (ExceptionNotFound $e) { 64304fd306cSNickeau return false; 64404fd306cSNickeau } 64504fd306cSNickeau if ($mime->toString() === Mime::SVG) { 64604fd306cSNickeau return true; 64704fd306cSNickeau } 64804fd306cSNickeau return false; 64904fd306cSNickeau 65004fd306cSNickeau } 65104fd306cSNickeau 65204fd306cSNickeau public function getMime(): Mime 65304fd306cSNickeau { 65404fd306cSNickeau return Mime::create(Mime::SVG); 65504fd306cSNickeau } 65604fd306cSNickeau 65704fd306cSNickeau 65804fd306cSNickeau public function setRequestedColor(ColorRgb $color): FetcherSvg 65904fd306cSNickeau { 66004fd306cSNickeau $this->color = $color; 66104fd306cSNickeau return $this; 66204fd306cSNickeau } 66304fd306cSNickeau 66404fd306cSNickeau /** 66504fd306cSNickeau * @throws ExceptionNotFound 66604fd306cSNickeau */ 66704fd306cSNickeau public function getRequestedColor(): ColorRgb 66804fd306cSNickeau { 66904fd306cSNickeau if ($this->color === null) { 67004fd306cSNickeau throw new ExceptionNotFound("No requested color"); 67104fd306cSNickeau } 67204fd306cSNickeau return $this->color; 67304fd306cSNickeau } 67404fd306cSNickeau 67504fd306cSNickeau /** 67604fd306cSNickeau * @param string $preserveAspectRatio - the aspect ratio of the svg 67704fd306cSNickeau * @return $this 67804fd306cSNickeau */ 67904fd306cSNickeau public function setRequestedPreserveAspectRatio(string $preserveAspectRatio): FetcherSvg 68004fd306cSNickeau { 68104fd306cSNickeau $this->preserveAspectRatio = $preserveAspectRatio; 68204fd306cSNickeau return $this; 68304fd306cSNickeau } 68404fd306cSNickeau 68504fd306cSNickeau 68604fd306cSNickeau /** 68704fd306cSNickeau * @var string|null - a name identifier that is added in the SVG 68804fd306cSNickeau */ 68904fd306cSNickeau private ?string $requestedName = null; 69004fd306cSNickeau 69104fd306cSNickeau /** 69204fd306cSNickeau * @var ?boolean do the svg should be optimized 69304fd306cSNickeau */ 69404fd306cSNickeau private ?bool $requestedOptimization = null; 69504fd306cSNickeau 69604fd306cSNickeau /** 69704fd306cSNickeau * @var XmlDocument|null 69804fd306cSNickeau */ 69904fd306cSNickeau private ?XmlDocument $xmlDocument = null; 70004fd306cSNickeau 70104fd306cSNickeau 70204fd306cSNickeau /** 70304fd306cSNickeau * The name: 70404fd306cSNickeau * * if this is a icon, this is the icon name of the {@link IconDownloader}. It's used to download the icon if not present. 70504fd306cSNickeau * * is used to add a data attribute in the svg to be able to select it for test purpose 70604fd306cSNickeau * 70704fd306cSNickeau * @param string $name 70804fd306cSNickeau * @return FetcherSvg 70904fd306cSNickeau */ 71004fd306cSNickeau public 71104fd306cSNickeau function setRequestedName(string $name): FetcherSvg 71204fd306cSNickeau { 71304fd306cSNickeau $this->requestedName = $name; 71404fd306cSNickeau return $this; 71504fd306cSNickeau } 71604fd306cSNickeau 71704fd306cSNickeau 71804fd306cSNickeau public 71904fd306cSNickeau function __toString() 72004fd306cSNickeau { 72104fd306cSNickeau if (isset($this->name)) { 72204fd306cSNickeau return $this->name; 72304fd306cSNickeau } 72404fd306cSNickeau if (isset($this->path)) { 72504fd306cSNickeau try { 72604fd306cSNickeau return $this->path->getLastNameWithoutExtension(); 72704fd306cSNickeau } catch (ExceptionNotFound $e) { 72804fd306cSNickeau LogUtility::internalError("root not possible, we should have a last name", self::CANONICAL); 72904fd306cSNickeau return "Anonymous"; 73004fd306cSNickeau } 73104fd306cSNickeau } 73204fd306cSNickeau return "Anonymous"; 73304fd306cSNickeau 73404fd306cSNickeau 73504fd306cSNickeau } 73604fd306cSNickeau 73704fd306cSNickeau 73804fd306cSNickeau /** 73904fd306cSNickeau * @param string $viewBox 74004fd306cSNickeau * @return string[] 74104fd306cSNickeau */ 74204fd306cSNickeau private function getViewBoxAttributes(string $viewBox): array 74304fd306cSNickeau { 74404fd306cSNickeau $attributes = explode(" ", $viewBox); 74504fd306cSNickeau if (sizeof($attributes) === 1) { 74604fd306cSNickeau /** 74704fd306cSNickeau * We may find also comma. Example: 74804fd306cSNickeau * viewBox="0,0,433.62,289.08" 74904fd306cSNickeau */ 75004fd306cSNickeau $attributes = explode(",", $viewBox); 75104fd306cSNickeau } 75204fd306cSNickeau return $attributes; 75304fd306cSNickeau } 75404fd306cSNickeau 75504fd306cSNickeau 75604fd306cSNickeau private function getXmlDocument(): XmlDocument 75704fd306cSNickeau { 75804fd306cSNickeau 75904fd306cSNickeau $this->buildXmlDocumentIfNeeded(); 76004fd306cSNickeau return $this->xmlDocument; 76104fd306cSNickeau } 76204fd306cSNickeau 76304fd306cSNickeau /** 76404fd306cSNickeau * Utility function 76504fd306cSNickeau * @return \DOMDocument 76604fd306cSNickeau */ 76704fd306cSNickeau public function getXmlDom(): \DOMDocument 76804fd306cSNickeau { 76904fd306cSNickeau return $this->getXmlDocument()->getDomDocument(); 77004fd306cSNickeau } 77104fd306cSNickeau 77204fd306cSNickeau /** 77304fd306cSNickeau * @throws ExceptionNotFound 77404fd306cSNickeau */ 77504fd306cSNickeau public function getRequestedName(): string 77604fd306cSNickeau { 77704fd306cSNickeau if ($this->requestedName === null) { 77804fd306cSNickeau throw new ExceptionNotFound("Name was not set"); 77904fd306cSNickeau } 78004fd306cSNickeau return $this->requestedName; 78104fd306cSNickeau } 78204fd306cSNickeau 78304fd306cSNickeau public function setPreserveStyle(bool $bool): FetcherSvg 78404fd306cSNickeau { 78504fd306cSNickeau $this->preserveStyle = $bool; 78604fd306cSNickeau return $this; 78704fd306cSNickeau } 78804fd306cSNickeau 78904fd306cSNickeau public function getRequestedPreserveStyleOrDefault(): bool 79004fd306cSNickeau { 79104fd306cSNickeau try { 79204fd306cSNickeau return $this->getRequestedPreserveStyle(); 79304fd306cSNickeau } catch (ExceptionNotFound $e) { 79404fd306cSNickeau return false; 79504fd306cSNickeau } 79604fd306cSNickeau } 79704fd306cSNickeau 79804fd306cSNickeau /** 79904fd306cSNickeau * @throws ExceptionNotFound 80004fd306cSNickeau */ 80104fd306cSNickeau public function getRequestedType(): string 80204fd306cSNickeau { 80304fd306cSNickeau if ($this->requestedType === null) { 80404fd306cSNickeau throw new ExceptionNotFound("The requested type was not specified"); 80504fd306cSNickeau } 80604fd306cSNickeau return $this->requestedType; 80704fd306cSNickeau } 80804fd306cSNickeau 80904fd306cSNickeau /** 81004fd306cSNickeau * @param string $markup - the svg as a string 81104fd306cSNickeau * @param string $name - a name identifier (used in diff) 81204fd306cSNickeau * @throws ExceptionBadSyntax 81304fd306cSNickeau */ 81404fd306cSNickeau private function setMarkup(string $markup, string $name): FetcherSvg 81504fd306cSNickeau { 81604fd306cSNickeau $this->name = $name; 81704fd306cSNickeau $this->buildXmlDocumentIfNeeded($markup); 81804fd306cSNickeau return $this; 81904fd306cSNickeau } 82004fd306cSNickeau 82104fd306cSNickeau 82204fd306cSNickeau public function setRequestedType(string $requestedType): FetcherSvg 82304fd306cSNickeau { 82404fd306cSNickeau $this->requestedType = $requestedType; 82504fd306cSNickeau return $this; 82604fd306cSNickeau } 82704fd306cSNickeau 82804fd306cSNickeau public function getRequestedHeight(): int 82904fd306cSNickeau { 83004fd306cSNickeau try { 83104fd306cSNickeau return $this->getDefaultWidhtAndHeightForIconAndTileIfNotSet(); 83204fd306cSNickeau } catch (ExceptionNotFound $e) { 83304fd306cSNickeau return parent::getRequestedHeight(); 83404fd306cSNickeau } 83504fd306cSNickeau } 83604fd306cSNickeau 83704fd306cSNickeau public function getRequestedWidth(): int 83804fd306cSNickeau { 83904fd306cSNickeau try { 84004fd306cSNickeau return $this->getDefaultWidhtAndHeightForIconAndTileIfNotSet(); 84104fd306cSNickeau } catch (ExceptionNotFound $e) { 84204fd306cSNickeau return parent::getRequestedWidth(); 84304fd306cSNickeau } 84404fd306cSNickeau } 84504fd306cSNickeau 84604fd306cSNickeau /** 84704fd306cSNickeau * @throws ExceptionBadSyntax 84804fd306cSNickeau * @throws ExceptionBadArgument 84904fd306cSNickeau * @throws ExceptionBadState 85004fd306cSNickeau * @throws ExceptionCompile 85104fd306cSNickeau */ 85204fd306cSNickeau public function process(): FetcherSvg 85304fd306cSNickeau { 85404fd306cSNickeau 85504fd306cSNickeau if ($this->processed) { 85604fd306cSNickeau LogUtility::internalError("The svg was already processed"); 85704fd306cSNickeau return $this; 85804fd306cSNickeau } 85904fd306cSNickeau 86004fd306cSNickeau $this->processed = true; 86104fd306cSNickeau 86204fd306cSNickeau // Handy variable 86304fd306cSNickeau $documentElement = $this->getXmlDocument()->getElement(); 86404fd306cSNickeau 86504fd306cSNickeau 86604fd306cSNickeau if ($this->getRequestedOptimizeOrDefault()) { 86704fd306cSNickeau $this->optimize(); 86804fd306cSNickeau } 86904fd306cSNickeau 87004fd306cSNickeau // Set the name (icon) attribute for test selection 87104fd306cSNickeau try { 87204fd306cSNickeau $name = $this->getRequestedNameOrDefault(); 87304fd306cSNickeau $documentElement->setAttribute('data-name', $name); 87404fd306cSNickeau } catch (ExceptionNotFound $e) { 87504fd306cSNickeau // ok no name 87604fd306cSNickeau } 87704fd306cSNickeau 87804fd306cSNickeau 87904fd306cSNickeau // Width requested 88004fd306cSNickeau try { 88104fd306cSNickeau $requestedWidth = $this->getRequestedWidth(); 88204fd306cSNickeau } catch (ExceptionNotFound $e) { 88304fd306cSNickeau $requestedWidth = null; 88404fd306cSNickeau } 88504fd306cSNickeau 88604fd306cSNickeau // Height requested 88704fd306cSNickeau try { 88804fd306cSNickeau $requestedHeight = $this->getRequestedHeight(); 88904fd306cSNickeau } catch (ExceptionNotFound $e) { 89004fd306cSNickeau $requestedHeight = null; 89104fd306cSNickeau } 89204fd306cSNickeau 89304fd306cSNickeau 89404fd306cSNickeau try { 89504fd306cSNickeau $requestedType = $this->getRequestedType(); 89604fd306cSNickeau } catch (ExceptionNotFound $e) { 89704fd306cSNickeau $requestedType = null; 89804fd306cSNickeau } 89904fd306cSNickeau 90004fd306cSNickeau /** 90104fd306cSNickeau * Svg Structure 90204fd306cSNickeau * 90304fd306cSNickeau * All attributes that are applied for all usage (output independent) 90404fd306cSNickeau * and that depends only on the structure of the icon 90504fd306cSNickeau * 90604fd306cSNickeau * Why ? Because {@link \syntax_plugin_combo_pageimage} 90704fd306cSNickeau * can be an icon or an illustrative image 90804fd306cSNickeau * 90904fd306cSNickeau */ 91004fd306cSNickeau $intrinsicWidth = $this->getIntrinsicWidth(); 91104fd306cSNickeau $intrinsicHeight = $this->getIntrinsicHeight(); 91204fd306cSNickeau 91304fd306cSNickeau 91404fd306cSNickeau $svgStructureType = $this->getInternalStructureType(); 91504fd306cSNickeau 91604fd306cSNickeau 91704fd306cSNickeau /** 91804fd306cSNickeau * Svg type 91904fd306cSNickeau * The svg type is the svg usage 92004fd306cSNickeau * How the svg should be shown (the usage) 92104fd306cSNickeau * 92204fd306cSNickeau * We need it to make the difference between an icon 92304fd306cSNickeau * * in a paragraph (the width and height are the same) 92404fd306cSNickeau * * as an illustration in a page image (the width and height may be not the same) 92504fd306cSNickeau */ 92604fd306cSNickeau if ($requestedType === null) { 92704fd306cSNickeau switch ($svgStructureType) { 92804fd306cSNickeau case FetcherSvg::ICON_TYPE: 92904fd306cSNickeau $requestedType = FetcherSvg::ICON_TYPE; 93004fd306cSNickeau break; 93104fd306cSNickeau default: 93204fd306cSNickeau $requestedType = FetcherSvg::ILLUSTRATION_TYPE; 93304fd306cSNickeau break; 93404fd306cSNickeau } 93504fd306cSNickeau } 93604fd306cSNickeau 93704fd306cSNickeau /** 93804fd306cSNickeau * A tag attributes to manage the add of style properties 93904fd306cSNickeau * in the style attribute 94004fd306cSNickeau */ 94104fd306cSNickeau $extraAttributes = TagAttributes::createEmpty(self::TAG); 94204fd306cSNickeau 94304fd306cSNickeau /** 94404fd306cSNickeau * Zoom occurs after the crop/dimenions setting if any 94504fd306cSNickeau */ 94604fd306cSNickeau try { 94704fd306cSNickeau $zoomFactor = $this->getRequestedZoom(); 94804fd306cSNickeau } catch (ExceptionNotFound $e) { 94904fd306cSNickeau if ($svgStructureType === FetcherSvg::ICON_TYPE && $requestedType === FetcherSvg::ILLUSTRATION_TYPE) { 95004fd306cSNickeau $zoomFactor = -4; 95104fd306cSNickeau } else { 95204fd306cSNickeau $zoomFactor = 1; // 0r 1 :) 95304fd306cSNickeau } 95404fd306cSNickeau } 95504fd306cSNickeau 95604fd306cSNickeau 95704fd306cSNickeau /** 95804fd306cSNickeau * Dimension processing (heigth, width, viewbox) 95904fd306cSNickeau * 96004fd306cSNickeau * ViewBox should exist 96104fd306cSNickeau * 96204fd306cSNickeau * Ratio / Width / Height Cropping happens via the viewbox 96304fd306cSNickeau * 96404fd306cSNickeau * Width and height used to set the viewBox of a svg 96504fd306cSNickeau * to crop it (In a raster image, there is not this distinction) 96604fd306cSNickeau * 96704fd306cSNickeau * We set the viewbox everytime: 96804fd306cSNickeau * If width and height are not the same, this is a crop 96904fd306cSNickeau * If width and height are the same, this is not a crop 97004fd306cSNickeau */ 97104fd306cSNickeau $targetWidth = $this->getTargetWidth(); 97204fd306cSNickeau $targetHeight = $this->getTargetHeight(); 97304fd306cSNickeau if ($this->isCropRequested() || $zoomFactor !== 1) { 97404fd306cSNickeau 97504fd306cSNickeau /** 97604fd306cSNickeau * ViewBox is the logical view 97704fd306cSNickeau * 97804fd306cSNickeau * with an icon case, we zoom out for illustation otherwise, this is ugly as the icon takes the whole place 97904fd306cSNickeau * 98004fd306cSNickeau * Zoom applies on the target/cropped dimension 98104fd306cSNickeau * so that we can center all at once in the next step 98204fd306cSNickeau */ 98304fd306cSNickeau 98404fd306cSNickeau /** 98504fd306cSNickeau * The crop happens when we set the height and width on the svg. 98604fd306cSNickeau * There is no need to manipulate the view box coordinate 98704fd306cSNickeau */ 98804fd306cSNickeau 98904fd306cSNickeau /** 99004fd306cSNickeau * Note: if the svg is an icon of width 24 with a viewbox of 0 0 24 24, 99104fd306cSNickeau * if you double the viewbox to 0 0 48 48, you have applied of -2 99204fd306cSNickeau * The icon is two times smaller smaller 99304fd306cSNickeau */ 99404fd306cSNickeau $viewBoxWidth = $this->getIntrinsicWidth(); 99504fd306cSNickeau $viewBoxHeight = $this->getIntrinsicHeight(); 99604fd306cSNickeau if ($zoomFactor < 0) { 99704fd306cSNickeau $viewBoxWidth = -$zoomFactor * $viewBoxWidth; 99804fd306cSNickeau $viewBoxHeight = -$zoomFactor * $viewBoxHeight; 99904fd306cSNickeau } else { 100004fd306cSNickeau $viewBoxWidth = $viewBoxWidth / $zoomFactor; 100104fd306cSNickeau $viewBoxHeight = $viewBoxHeight / $zoomFactor; 100204fd306cSNickeau } 100304fd306cSNickeau 100404fd306cSNickeau 100504fd306cSNickeau /** 100604fd306cSNickeau * Center 100704fd306cSNickeau * 100804fd306cSNickeau * We center by moving the origin (ie x and y) 100904fd306cSNickeau */ 101004fd306cSNickeau $x = -($viewBoxWidth - $intrinsicWidth) / 2; 101104fd306cSNickeau $y = -($viewBoxHeight - $intrinsicHeight) / 2; 101204fd306cSNickeau $documentElement->setAttribute(FetcherSvg::VIEW_BOX, "$x $y $viewBoxWidth $viewBoxHeight"); 101304fd306cSNickeau 101404fd306cSNickeau } else { 101504fd306cSNickeau $viewBox = $documentElement->getAttribute(FetcherSvg::VIEW_BOX); 101604fd306cSNickeau if (empty($viewBox)) { 101704fd306cSNickeau // viewbox is mandatory 101804fd306cSNickeau $documentElement->setAttribute(FetcherSvg::VIEW_BOX, "0 0 {$this->getIntrinsicWidth()} {$this->getIntrinsicHeight()}"); 101904fd306cSNickeau } 102004fd306cSNickeau } 102104fd306cSNickeau /** 102204fd306cSNickeau * Dimension are mandatory 102304fd306cSNickeau * Why ? 102404fd306cSNickeau * - to not take the dimension of the parent - Setting the width and height is important, otherwise it takes the dimension of the parent (that are generally a squared) 102504fd306cSNickeau * - to show the crop 102604fd306cSNickeau * - to have internal calculate dimension otherwise, it's tiny 102704fd306cSNickeau * - To have an internal width and not shrink on the css property `width: auto !important;` of a table 102804fd306cSNickeau * - To have an internal height and not shrink on the css property `height: auto !important;` of a table 102904fd306cSNickeau * - Using a icon in the navbrand component of bootstrap require the set of width and height otherwise the svg has a calculated width of null and the bar component are below the brand text 103004fd306cSNickeau * - ... 103104fd306cSNickeau * */ 103204fd306cSNickeau $documentElement 103304fd306cSNickeau ->setAttribute(Dimension::WIDTH_KEY, $targetWidth) 103404fd306cSNickeau ->setAttribute(Dimension::HEIGHT_KEY, $targetHeight); 103504fd306cSNickeau 103604fd306cSNickeau /** 103704fd306cSNickeau * Css styling due to dimension 103804fd306cSNickeau */ 103904fd306cSNickeau switch ($requestedType) { 104004fd306cSNickeau case FetcherSvg::ICON_TYPE: 104104fd306cSNickeau case FetcherSvg::TILE_TYPE: 104204fd306cSNickeau 104304fd306cSNickeau if ($targetWidth !== $targetHeight) { 104404fd306cSNickeau /** 104504fd306cSNickeau * Check if the widht and height are the same 104604fd306cSNickeau * 104704fd306cSNickeau * Note: this is not the case for an illustration, 104804fd306cSNickeau * they may be different 104904fd306cSNickeau * They are not the width and height of the icon but 105004fd306cSNickeau * the width and height of the viewbox 105104fd306cSNickeau */ 105204fd306cSNickeau LogUtility::info("An icon or tile is defined as having the same dimension but the svg ($this) has a target width of ($targetWidth) that is different from the target height ($targetHeight). The icon will be cropped."); 105304fd306cSNickeau } 105404fd306cSNickeau 105504fd306cSNickeau break; 105604fd306cSNickeau default: 105704fd306cSNickeau /** 105804fd306cSNickeau * Illustration / Image 105904fd306cSNickeau */ 106004fd306cSNickeau /** 106104fd306cSNickeau * Responsive SVG 106204fd306cSNickeau */ 106304fd306cSNickeau try { 106404fd306cSNickeau $aspectRatio = $this->getRequestedPreserveAspectRatio(); 106504fd306cSNickeau } catch (ExceptionNotFound $e) { 106604fd306cSNickeau /** 106704fd306cSNickeau * 106804fd306cSNickeau * Keep the same height 106904fd306cSNickeau * Image in the Middle and border deleted when resizing 107004fd306cSNickeau * https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio 107104fd306cSNickeau * Default is xMidYMid meet 107204fd306cSNickeau */ 107304fd306cSNickeau $aspectRatio = SiteConfig::getConfValue(FetcherSvg::CONF_PRESERVE_ASPECT_RATIO_DEFAULT, "xMidYMid slice"); 107404fd306cSNickeau } 107504fd306cSNickeau $documentElement->setAttribute("preserveAspectRatio", $aspectRatio); 107604fd306cSNickeau 107704fd306cSNickeau /** 107804fd306cSNickeau * Note on dimension width and height 107904fd306cSNickeau * Width and height element attribute are in reality css style properties. 108004fd306cSNickeau * ie the max-width style 108104fd306cSNickeau * They are treated in {@link PluginUtility::processStyle()} 108204fd306cSNickeau */ 108304fd306cSNickeau 108404fd306cSNickeau /** 108504fd306cSNickeau * Adapt to the container by default 108604fd306cSNickeau * Height `auto` and not `100%` otherwise you get a layout shift 108704fd306cSNickeau */ 108804fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("width", "100%"); 108904fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("height", "auto"); 109004fd306cSNickeau 109104fd306cSNickeau 109204fd306cSNickeau if ($requestedWidth !== null) { 109304fd306cSNickeau 109404fd306cSNickeau /** 109504fd306cSNickeau * If a dimension was set, it's seen by default as a max-width 109604fd306cSNickeau * If it should not such as in a card, this property is already set 109704fd306cSNickeau * and is not overwritten 109804fd306cSNickeau */ 109904fd306cSNickeau try { 110004fd306cSNickeau $widthInPixel = ConditionalLength::createFromString($requestedWidth)->toPixelNumber(); 110104fd306cSNickeau } catch (ExceptionCompile $e) { 110204fd306cSNickeau LogUtility::msg("The requested width $requestedWidth could not be converted to pixel. It returns the following error ({$e->getMessage()}). Processing was stopped"); 110304fd306cSNickeau return $this; 110404fd306cSNickeau } 110504fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("max-width", "{$widthInPixel}px"); 110604fd306cSNickeau 110704fd306cSNickeau } 110804fd306cSNickeau 110904fd306cSNickeau 111004fd306cSNickeau if ($requestedHeight !== null) { 111104fd306cSNickeau /** 111204fd306cSNickeau * If a dimension was set, it's seen by default as a max-width 111304fd306cSNickeau * If it should not such as in a card, this property is already set 111404fd306cSNickeau * and is not overwritten 111504fd306cSNickeau */ 111604fd306cSNickeau try { 111704fd306cSNickeau $heightInPixel = ConditionalLength::createFromString($requestedHeight)->toPixelNumber(); 111804fd306cSNickeau } catch (ExceptionCompile $e) { 111904fd306cSNickeau LogUtility::msg("The requested height $requestedHeight could not be converted to pixel. It returns the following error ({$e->getMessage()}). Processing was stopped"); 112004fd306cSNickeau return $this; 112104fd306cSNickeau } 112204fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("max-height", "{$heightInPixel}px"); 112304fd306cSNickeau 112404fd306cSNickeau 112504fd306cSNickeau } 112604fd306cSNickeau 112704fd306cSNickeau break; 112804fd306cSNickeau } 112904fd306cSNickeau 113004fd306cSNickeau 113104fd306cSNickeau switch ($svgStructureType) { 113204fd306cSNickeau case FetcherSvg::ICON_TYPE: 113304fd306cSNickeau case FetcherSvg::TILE_TYPE: 113404fd306cSNickeau /** 113504fd306cSNickeau * Determine if this is a: 113604fd306cSNickeau * * fill one color 113704fd306cSNickeau * * fill two colors 113804fd306cSNickeau * * or stroke svg icon 113904fd306cSNickeau * 114004fd306cSNickeau * The color can be set: 114104fd306cSNickeau * * on fill (surface) 114204fd306cSNickeau * * on stroke (line) 114304fd306cSNickeau * 114404fd306cSNickeau * If the stroke attribute is not present this is a fill icon 114504fd306cSNickeau */ 114604fd306cSNickeau $svgColorType = FetcherSvg::COLOR_TYPE_FILL_SOLID; 114704fd306cSNickeau if ($documentElement->hasAttribute(FetcherSvg::STROKE_ATTRIBUTE)) { 114804fd306cSNickeau $svgColorType = FetcherSvg::COLOR_TYPE_STROKE_OUTLINE; 114904fd306cSNickeau } 115004fd306cSNickeau /** 115104fd306cSNickeau * Double color icon ? 115204fd306cSNickeau */ 115304fd306cSNickeau $isDoubleColor = false; 115404fd306cSNickeau if ($svgColorType === FetcherSvg::COLOR_TYPE_FILL_SOLID) { 115504fd306cSNickeau $svgFillsElement = $this->getXmlDocument()->xpath("//*[@fill]"); 115604fd306cSNickeau $fillColors = []; 115704fd306cSNickeau for ($i = 0; $i < $svgFillsElement->length; $i++) { 115804fd306cSNickeau /** 115904fd306cSNickeau * @var DOMElement $nodeElement 116004fd306cSNickeau */ 116104fd306cSNickeau $nodeElement = $svgFillsElement[$i]; 116204fd306cSNickeau $value = $nodeElement->getAttribute("fill"); 116304fd306cSNickeau if ($value !== "none") { 116404fd306cSNickeau /** 116504fd306cSNickeau * Icon may have none alongside colors 116604fd306cSNickeau * Example: 116704fd306cSNickeau */ 116804fd306cSNickeau $fillColors[$value] = $value; 116904fd306cSNickeau } 117004fd306cSNickeau } 117104fd306cSNickeau if (sizeof($fillColors) > 1) { 117204fd306cSNickeau $isDoubleColor = true; 117304fd306cSNickeau } 117404fd306cSNickeau } 117504fd306cSNickeau 117604fd306cSNickeau /** 117704fd306cSNickeau * CurrentColor 117804fd306cSNickeau * 117904fd306cSNickeau * By default, the icon should have this property when downloaded 118004fd306cSNickeau * but if this not the case (such as for Material design), we set them 118104fd306cSNickeau * 118204fd306cSNickeau * Feather set it on the stroke 118304fd306cSNickeau * Example: view-source:https://raw.githubusercontent.com/feathericons/feather/master/icons/airplay.svg 118404fd306cSNickeau * <svg 118504fd306cSNickeau * fill="none" 118604fd306cSNickeau * stroke="currentColor"> 118704fd306cSNickeau */ 118804fd306cSNickeau if (!$isDoubleColor && !$documentElement->hasAttribute("fill")) { 118904fd306cSNickeau 119004fd306cSNickeau /** 119104fd306cSNickeau * Note: if fill was not set, the default color would be black 119204fd306cSNickeau */ 119304fd306cSNickeau $documentElement->setAttribute("fill", FetcherSvg::CURRENT_COLOR); 119404fd306cSNickeau 119504fd306cSNickeau } 119604fd306cSNickeau 119704fd306cSNickeau 119804fd306cSNickeau /** 119904fd306cSNickeau * Eva/Carbon Source Icon are not optimized at the source 120004fd306cSNickeau * Example: 120104fd306cSNickeau * * eva:facebook-fill 120204fd306cSNickeau * * carbon:logo-tumblr (https://github.com/carbon-design-system/carbon/issues/5568) 120304fd306cSNickeau * 120404fd306cSNickeau * We delete the rectangle 120504fd306cSNickeau * Style should have already been deleted by the optimization 120604fd306cSNickeau * 120704fd306cSNickeau * This optimization should happen if the color is set 120804fd306cSNickeau * or not because we set the color value to `currentColor` 120904fd306cSNickeau * 121004fd306cSNickeau * If the rectangle stay, we just see a black rectangle 121104fd306cSNickeau */ 121204fd306cSNickeau try { 121304fd306cSNickeau $path = $this->getSourcePath(); 121404fd306cSNickeau $pathString = $path->toAbsolutePath()->toAbsoluteId(); 121504fd306cSNickeau if ( 121604fd306cSNickeau preg_match("/carbon|eva/i", $pathString) === 1 121704fd306cSNickeau ) { 121804fd306cSNickeau XmlSystems::deleteAllElementsByName("rect", $this->getXmlDocument()); 121904fd306cSNickeau } 122004fd306cSNickeau } catch (ExceptionNotFound $e) { 122104fd306cSNickeau // ok 122204fd306cSNickeau } 122304fd306cSNickeau 122404fd306cSNickeau 122504fd306cSNickeau $color = null; 122604fd306cSNickeau try { 122704fd306cSNickeau $color = $this->getRequestedColor(); 122804fd306cSNickeau } catch (ExceptionNotFound $e) { 122904fd306cSNickeau if ($requestedType === FetcherSvg::ILLUSTRATION_TYPE) { 123004fd306cSNickeau $primaryColor = Site::getPrimaryColorValue(); 123104fd306cSNickeau if ($primaryColor !== null) { 123204fd306cSNickeau $color = ColorRgb::createFromString($primaryColor); 123304fd306cSNickeau } 123404fd306cSNickeau } 123504fd306cSNickeau } 123604fd306cSNickeau 123704fd306cSNickeau 123804fd306cSNickeau /** 123904fd306cSNickeau * Color 124004fd306cSNickeau * Color applies only if this is an icon. 124104fd306cSNickeau * 124204fd306cSNickeau */ 124304fd306cSNickeau if ($color !== null) { 124404fd306cSNickeau /** 124504fd306cSNickeau * 124604fd306cSNickeau * We say that this is used only for an icon (<72 px) 124704fd306cSNickeau * 124804fd306cSNickeau * Not that an icon svg file can also be used as {@link \syntax_plugin_combo_pageimage} 124904fd306cSNickeau * 125004fd306cSNickeau * We don't set it as a styling attribute 125104fd306cSNickeau * because it's not taken into account if the 125204fd306cSNickeau * svg is used as a background image 125304fd306cSNickeau * fill or stroke should have at minimum "currentColor" 125404fd306cSNickeau */ 125504fd306cSNickeau $colorValue = $color->toCssValue(); 125604fd306cSNickeau 125704fd306cSNickeau 125804fd306cSNickeau switch ($svgColorType) { 125904fd306cSNickeau case FetcherSvg::COLOR_TYPE_FILL_SOLID: 126004fd306cSNickeau 126104fd306cSNickeau if (!$isDoubleColor) { 126204fd306cSNickeau 126304fd306cSNickeau $documentElement->setAttribute("fill", $colorValue); 126404fd306cSNickeau 126504fd306cSNickeau if ($colorValue !== FetcherSvg::CURRENT_COLOR) { 126604fd306cSNickeau /** 126704fd306cSNickeau * Update the fill property on sub-path 126804fd306cSNickeau * If the fill is set on sub-path, it will not work 126904fd306cSNickeau * 127004fd306cSNickeau * fill may be set on group or whatever 127104fd306cSNickeau */ 127204fd306cSNickeau $svgPaths = $this->getXmlDocument()->xpath("//*[local-name()='path' or local-name()='g']"); 127304fd306cSNickeau for ($i = 0; $i < $svgPaths->length; $i++) { 127404fd306cSNickeau /** 127504fd306cSNickeau * @var DOMElement $nodeElement 127604fd306cSNickeau */ 127704fd306cSNickeau $nodeElement = $svgPaths[$i]; 127804fd306cSNickeau $value = $nodeElement->getAttribute("fill"); 127904fd306cSNickeau if ($value !== "none") { 128004fd306cSNickeau if ($nodeElement->parentNode->tagName !== "svg") { 128104fd306cSNickeau $nodeElement->setAttribute("fill", FetcherSvg::CURRENT_COLOR); 128204fd306cSNickeau } else { 128304fd306cSNickeau $this->getXmlDocument()->removeAttributeValue("fill", $nodeElement); 128404fd306cSNickeau } 128504fd306cSNickeau } 128604fd306cSNickeau } 128704fd306cSNickeau 128804fd306cSNickeau } 128904fd306cSNickeau } else { 129004fd306cSNickeau // double color 129104fd306cSNickeau $firsFillElement = $this->getXmlDocument()->xpath("//*[@fill][1]")->item(0); 129204fd306cSNickeau if ($firsFillElement instanceof DOMElement) { 129304fd306cSNickeau $firsFillElement->setAttribute("fill", $colorValue); 129404fd306cSNickeau } 129504fd306cSNickeau } 129604fd306cSNickeau break; 129704fd306cSNickeau 129804fd306cSNickeau case FetcherSvg::COLOR_TYPE_STROKE_OUTLINE: 129904fd306cSNickeau $documentElement->setAttribute("fill", "none"); 130004fd306cSNickeau $documentElement->setAttribute(FetcherSvg::STROKE_ATTRIBUTE, $colorValue); 130104fd306cSNickeau 130204fd306cSNickeau if ($colorValue !== FetcherSvg::CURRENT_COLOR) { 130304fd306cSNickeau /** 130404fd306cSNickeau * Delete the stroke property on sub-path 130504fd306cSNickeau */ 130604fd306cSNickeau // if the fill is set on sub-path, it will not work 130704fd306cSNickeau $svgPaths = $this->getXmlDocument()->xpath("//*[local-name()='path']"); 130804fd306cSNickeau for ($i = 0; $i < $svgPaths->length; $i++) { 130904fd306cSNickeau /** 131004fd306cSNickeau * @var DOMElement $nodeElement 131104fd306cSNickeau */ 131204fd306cSNickeau $nodeElement = $svgPaths[$i]; 131304fd306cSNickeau $value = $nodeElement->getAttribute(FetcherSvg::STROKE_ATTRIBUTE); 131404fd306cSNickeau if ($value !== "none") { 131504fd306cSNickeau $this->getXmlDocument()->removeAttributeValue(FetcherSvg::STROKE_ATTRIBUTE, $nodeElement); 131604fd306cSNickeau } else { 131704fd306cSNickeau $this->getXmlDocument()->removeNode($nodeElement); 131804fd306cSNickeau } 131904fd306cSNickeau } 132004fd306cSNickeau 132104fd306cSNickeau } 132204fd306cSNickeau break; 132304fd306cSNickeau } 132404fd306cSNickeau 132504fd306cSNickeau } 132604fd306cSNickeau break; 132704fd306cSNickeau 132804fd306cSNickeau } 132904fd306cSNickeau 133004fd306cSNickeau 133104fd306cSNickeau /** 133204fd306cSNickeau * Set the attributes to the root element 133304fd306cSNickeau * Svg attribute are case sensitive 133404fd306cSNickeau * Styling 133504fd306cSNickeau */ 133604fd306cSNickeau $extraAttributeAsArray = $extraAttributes->toHtmlArray(); 133704fd306cSNickeau foreach ($extraAttributeAsArray as $name => $value) { 133804fd306cSNickeau $documentElement->setAttribute($name, $value); 133904fd306cSNickeau } 134004fd306cSNickeau 134104fd306cSNickeau /** 134204fd306cSNickeau * Class 134304fd306cSNickeau */ 134404fd306cSNickeau try { 134504fd306cSNickeau $class = $this->getRequestedClass(); 134604fd306cSNickeau $documentElement->addClass($class); 134704fd306cSNickeau } catch (ExceptionNotFound $e) { 134804fd306cSNickeau // no class 134904fd306cSNickeau } 135004fd306cSNickeau // add class with svg type 135104fd306cSNickeau $documentElement 135204fd306cSNickeau ->addClass(StyleAttribute::addComboStrapSuffix(self::TAG)) 135304fd306cSNickeau ->addClass(StyleAttribute::addComboStrapSuffix(self::TAG . "-" . $requestedType)); 135404fd306cSNickeau // Add a class on each path for easy styling 135504fd306cSNickeau try { 135604fd306cSNickeau $name = $this->getRequestedNameOrDefault(); 135704fd306cSNickeau $svgPaths = $documentElement->querySelectorAll('path'); 135804fd306cSNickeau for ($i = 0; 135904fd306cSNickeau $i < count($svgPaths); 136004fd306cSNickeau $i++) { 136104fd306cSNickeau $element = $svgPaths[$i]; 136204fd306cSNickeau $stylingClass = $name . "-" . $i; 136304fd306cSNickeau $element->addClass($stylingClass); 136404fd306cSNickeau } 136504fd306cSNickeau } catch (ExceptionNotFound $e) { 136604fd306cSNickeau // no name 136704fd306cSNickeau } 136804fd306cSNickeau 136904fd306cSNickeau return $this; 137004fd306cSNickeau 137104fd306cSNickeau } 137204fd306cSNickeau 137304fd306cSNickeau 137404fd306cSNickeau public function getFetcherName(): string 137504fd306cSNickeau { 137604fd306cSNickeau return self::CANONICAL; 137704fd306cSNickeau } 137804fd306cSNickeau 137904fd306cSNickeau /** 138004fd306cSNickeau * @throws ExceptionBadArgument 138104fd306cSNickeau * @throws ExceptionBadSyntax 138204fd306cSNickeau * @throws ExceptionCompile 138304fd306cSNickeau */ 138404fd306cSNickeau public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherImage 138504fd306cSNickeau { 138604fd306cSNickeau 138704fd306cSNickeau foreach (array_keys($tagAttributes->getComponentAttributes()) as $svgAttribute) { 138804fd306cSNickeau $svgAttribute = strtolower($svgAttribute); 138904fd306cSNickeau switch ($svgAttribute) { 139004fd306cSNickeau case Dimension::WIDTH_KEY: 139104fd306cSNickeau case Dimension::HEIGHT_KEY: 139204fd306cSNickeau /** 139304fd306cSNickeau * Length may be defined with CSS unit 139404fd306cSNickeau * https://www.w3.org/TR/SVG2/coords.html#Units 139504fd306cSNickeau */ 139604fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 139704fd306cSNickeau try { 139804fd306cSNickeau $lengthInt = ConditionalLength::createFromString($value)->toPixelNumber(); 139904fd306cSNickeau } catch (ExceptionBadArgument $e) { 140004fd306cSNickeau LogUtility::error("The $svgAttribute value ($value) of the svg ($this) is not an integer", self::CANONICAL); 140104fd306cSNickeau continue 2; 140204fd306cSNickeau } 140304fd306cSNickeau if ($svgAttribute === Dimension::WIDTH_KEY) { 140404fd306cSNickeau $this->setRequestedWidth($lengthInt); 140504fd306cSNickeau } else { 140604fd306cSNickeau $this->setRequestedHeight($lengthInt); 140704fd306cSNickeau } 140804fd306cSNickeau continue 2; 140904fd306cSNickeau case Dimension::ZOOM_ATTRIBUTE; 141004fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 141104fd306cSNickeau try { 141204fd306cSNickeau $lengthFloat = DataType::toFloat($value); 141304fd306cSNickeau } catch (ExceptionBadArgument $e) { 141404fd306cSNickeau LogUtility::error("The $svgAttribute value ($value) of the svg ($this) is not a float", self::CANONICAL); 141504fd306cSNickeau continue 2; 141604fd306cSNickeau } 141704fd306cSNickeau $this->setRequestedZoom($lengthFloat); 141804fd306cSNickeau continue 2; 141904fd306cSNickeau case ColorRgb::COLOR: 142004fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 142104fd306cSNickeau try { 142204fd306cSNickeau $color = ColorRgb::createFromString($value); 142304fd306cSNickeau } catch (ExceptionBadArgument $e) { 142404fd306cSNickeau LogUtility::error("The $svgAttribute value ($value) of the svg ($this) is not an valid color", self::CANONICAL); 142504fd306cSNickeau continue 2; 142604fd306cSNickeau } 142704fd306cSNickeau $this->setRequestedColor($color); 142804fd306cSNickeau continue 2; 142904fd306cSNickeau case TagAttributes::TYPE_KEY: 143004fd306cSNickeau $value = $tagAttributes->getValue($svgAttribute); 143104fd306cSNickeau $this->setRequestedType($value); 143204fd306cSNickeau continue 2; 143304fd306cSNickeau case self::REQUESTED_PRESERVE_ATTRIBUTE: 143404fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 143504fd306cSNickeau if ($value === "style") { 143604fd306cSNickeau $preserve = true; 143704fd306cSNickeau } else { 143804fd306cSNickeau $preserve = false; 143904fd306cSNickeau } 144004fd306cSNickeau $this->setPreserveStyle($preserve); 144104fd306cSNickeau continue 2; 144204fd306cSNickeau case self::NAME_ATTRIBUTE: 144304fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 144404fd306cSNickeau $this->setRequestedName($value); 144504fd306cSNickeau continue 2; 144604fd306cSNickeau case TagAttributes::CLASS_KEY: 144704fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 144804fd306cSNickeau $this->setRequestedClass($value); 144904fd306cSNickeau continue 2; 145004fd306cSNickeau case strtolower(self::REQUESTED_PRESERVE_ASPECT_RATIO_KEY): 145104fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 145204fd306cSNickeau $this->setRequestedPreserveAspectRatio($value); 145304fd306cSNickeau continue 2; 145404fd306cSNickeau } 145504fd306cSNickeau 145604fd306cSNickeau } 145704fd306cSNickeau 145804fd306cSNickeau /** 145904fd306cSNickeau * Icon case 146004fd306cSNickeau */ 146104fd306cSNickeau try { 146204fd306cSNickeau $iconDownload = 1463*70bbd7f1Sgerardnico !$tagAttributes->hasAttribute(MediaMarkup::$MEDIA_QUERY_PARAMETER) && 146404fd306cSNickeau $this->getRequestedType() === self::ICON_TYPE 146504fd306cSNickeau && $this->getRequestedName() !== null; 146604fd306cSNickeau if ($iconDownload) { 146704fd306cSNickeau try { 146804fd306cSNickeau $dokuPath = $this->downloadAndGetIconPath(); 146904fd306cSNickeau } catch (ExceptionCompile $e) { 147004fd306cSNickeau throw new ExceptionBadArgument("We can't get the icon path. Error: {$e->getMessage()}. (ie media or icon name attribute is mandatory).", self::CANONICAL, 1, $e); 147104fd306cSNickeau } 147204fd306cSNickeau $this->setSourcePath($dokuPath); 147304fd306cSNickeau 147404fd306cSNickeau } 147504fd306cSNickeau } catch (ExceptionNotFound $e) { 147604fd306cSNickeau // no requested type or name 147704fd306cSNickeau } 147804fd306cSNickeau 147904fd306cSNickeau /** 148004fd306cSNickeau * Raw Trait 148104fd306cSNickeau */ 148204fd306cSNickeau $this->buildOriginalPathFromTagAttributes($tagAttributes); 148304fd306cSNickeau parent::buildFromTagAttributes($tagAttributes); 148404fd306cSNickeau return $this; 148504fd306cSNickeau } 148604fd306cSNickeau 148704fd306cSNickeau /** 148804fd306cSNickeau * @throws ExceptionBadArgument 148904fd306cSNickeau * @throws ExceptionCompile 149004fd306cSNickeau * @throws ExceptionBadSyntax 149104fd306cSNickeau * @throws ExceptionNotFound 149204fd306cSNickeau */ 149304fd306cSNickeau private function downloadAndGetIconPath(): WikiPath 149404fd306cSNickeau { 149504fd306cSNickeau /** 149604fd306cSNickeau * It may be a Svg icon that we needs to download 149704fd306cSNickeau */ 149804fd306cSNickeau try { 149904fd306cSNickeau $requestedType = $this->getRequestedType(); 150004fd306cSNickeau $requestedName = $this->getRequestedName(); 150104fd306cSNickeau } catch (ExceptionNotFound $e) { 150204fd306cSNickeau throw new ExceptionNotFound("No path was defined and no icon name was defined"); 150304fd306cSNickeau } 150404fd306cSNickeau if ($requestedType !== self::ICON_TYPE) { 150504fd306cSNickeau throw new ExceptionNotFound("No original path was set and no icon was defined"); 150604fd306cSNickeau } 150704fd306cSNickeau 150804fd306cSNickeau try { 150904fd306cSNickeau $iconDownloader = IconDownloader::createFromName($requestedName); 151004fd306cSNickeau } catch (ExceptionBadArgument $e) { 151104fd306cSNickeau throw new ExceptionNotFound("The name ($requestedName) is not a valid icon name. Error: ({$e->getMessage()}.", self::CANONICAL, 1, $e); 151204fd306cSNickeau } 151304fd306cSNickeau $originalPath = $iconDownloader->getPath(); 151404fd306cSNickeau if (FileSystems::exists($originalPath)) { 151504fd306cSNickeau return $originalPath; 151604fd306cSNickeau } 151704fd306cSNickeau try { 151804fd306cSNickeau $iconDownloader->download(); 151904fd306cSNickeau } catch (ExceptionCompile $e) { 152004fd306cSNickeau throw new ExceptionCompile("The icon ($requestedName) could not be downloaded. Error: ({$e->getMessage()}.", self::CANONICAL); 152104fd306cSNickeau } 152204fd306cSNickeau $this->setSourcePath($originalPath); 152304fd306cSNickeau return $originalPath; 152404fd306cSNickeau } 152504fd306cSNickeau 152604fd306cSNickeau /** 152704fd306cSNickeau * This is used to add a name and class to the svg to make selection more easy 152804fd306cSNickeau * @throws ExceptionBadState 152904fd306cSNickeau * @throws ExceptionNotFound 153004fd306cSNickeau */ 153104fd306cSNickeau private function getRequestedNameOrDefault(): string 153204fd306cSNickeau { 153304fd306cSNickeau try { 153404fd306cSNickeau return $this->getRequestedName(); 153504fd306cSNickeau } catch (ExceptionNotFound $e) { 153604fd306cSNickeau return $this->getSourcePath()->getLastNameWithoutExtension(); 153704fd306cSNickeau } 153804fd306cSNickeau } 153904fd306cSNickeau 154004fd306cSNickeau /** 154104fd306cSNickeau * @return bool - true if no width or height was requested 154204fd306cSNickeau */ 154304fd306cSNickeau private function norWidthNorHeightWasRequested(): bool 154404fd306cSNickeau { 154504fd306cSNickeau 154604fd306cSNickeau if ($this->requestedWidth !== null) { 154704fd306cSNickeau return false; 154804fd306cSNickeau } 154904fd306cSNickeau if ($this->requestedHeight !== null) { 155004fd306cSNickeau return false; 155104fd306cSNickeau } 155204fd306cSNickeau return true; 155304fd306cSNickeau 155404fd306cSNickeau } 155504fd306cSNickeau 155604fd306cSNickeau /** 155704fd306cSNickeau * @throws ExceptionNotFound 155804fd306cSNickeau */ 155904fd306cSNickeau private function getRequestedZoom(): float 156004fd306cSNickeau { 156104fd306cSNickeau $zoom = $this->zoomFactor; 156204fd306cSNickeau if ($zoom === null) { 156304fd306cSNickeau throw new ExceptionNotFound("No zoom requested"); 156404fd306cSNickeau } 156504fd306cSNickeau return $zoom; 156604fd306cSNickeau } 156704fd306cSNickeau 156804fd306cSNickeau public function setRequestedZoom(float $zoomFactor): FetcherSvg 156904fd306cSNickeau { 157004fd306cSNickeau $this->zoomFactor = $zoomFactor; 157104fd306cSNickeau return $this; 157204fd306cSNickeau } 157304fd306cSNickeau 157404fd306cSNickeau public function setRequestedClass(string $value): FetcherSvg 157504fd306cSNickeau { 157604fd306cSNickeau $this->requestedClass = $value; 157704fd306cSNickeau return $this; 157804fd306cSNickeau 157904fd306cSNickeau } 158004fd306cSNickeau 158104fd306cSNickeau /** 158204fd306cSNickeau * @throws ExceptionNotFound 158304fd306cSNickeau */ 158404fd306cSNickeau private function getRequestedClass(): string 158504fd306cSNickeau { 158604fd306cSNickeau if ($this->requestedClass === null) { 158704fd306cSNickeau throw new ExceptionNotFound("No class was set"); 158804fd306cSNickeau } 158904fd306cSNickeau return $this->requestedClass; 159004fd306cSNickeau } 159104fd306cSNickeau 159204fd306cSNickeau /** 159304fd306cSNickeau * Analyse and set the mandatory intrinsic dimensions 159404fd306cSNickeau * @throws ExceptionBadSyntax 159504fd306cSNickeau */ 159604fd306cSNickeau private function setIntrinsicDimensions() 159704fd306cSNickeau { 159804fd306cSNickeau $this->setIntrinsicHeight() 159904fd306cSNickeau ->setIntrinsicWidth(); 160004fd306cSNickeau } 160104fd306cSNickeau 160204fd306cSNickeau /** 160304fd306cSNickeau * @throws ExceptionBadSyntax 160404fd306cSNickeau */ 160504fd306cSNickeau private function setIntrinsicHeight(): FetcherSvg 160604fd306cSNickeau { 160704fd306cSNickeau $viewBox = $this->getXmlDocument()->getDomDocument()->documentElement->getAttribute(FetcherSvg::VIEW_BOX); 160804fd306cSNickeau if ($viewBox !== "") { 160904fd306cSNickeau $attributes = $this->getViewBoxAttributes($viewBox); 161004fd306cSNickeau $viewBoxHeight = $attributes[3]; 161104fd306cSNickeau try { 161204fd306cSNickeau /** 161304fd306cSNickeau * Ceil because we want to see a border if there is one 161404fd306cSNickeau */ 161504fd306cSNickeau $this->intrinsicHeight = DataType::toIntegerCeil($viewBoxHeight); 161604fd306cSNickeau return $this; 161704fd306cSNickeau } catch (ExceptionBadArgument $e) { 161804fd306cSNickeau throw new ExceptionBadSyntax("The media height ($viewBoxHeight) of the svg image ($this) is not a valid integer value"); 161904fd306cSNickeau } 162004fd306cSNickeau } 162104fd306cSNickeau /** 162204fd306cSNickeau * Case with some icon such as 162304fd306cSNickeau * https://raw.githubusercontent.com/fefanto/fontaudio/master/svgs/fad-random-1dice.svg 162404fd306cSNickeau */ 162504fd306cSNickeau $height = $this->getXmlDocument()->getDomDocument()->documentElement->getAttribute("height"); 162604fd306cSNickeau if ($height === "") { 162704fd306cSNickeau throw new ExceptionBadSyntax("The svg ($this) does not have a viewBox or height attribute, the intrinsic height cannot be determined"); 162804fd306cSNickeau } 162904fd306cSNickeau try { 163004fd306cSNickeau $this->intrinsicHeight = DataType::toInteger($height); 163104fd306cSNickeau } catch (ExceptionBadArgument $e) { 163204fd306cSNickeau throw new ExceptionBadSyntax("The media width ($height) of the svg image ($this) is not a valid integer value"); 163304fd306cSNickeau } 163404fd306cSNickeau return $this; 163504fd306cSNickeau } 163604fd306cSNickeau 163704fd306cSNickeau /** 163804fd306cSNickeau * @throws ExceptionBadSyntax 163904fd306cSNickeau */ 164004fd306cSNickeau private function setIntrinsicWidth(): FetcherSvg 164104fd306cSNickeau { 164204fd306cSNickeau $viewBox = $this->getXmlDom()->documentElement->getAttribute(FetcherSvg::VIEW_BOX); 164304fd306cSNickeau if ($viewBox !== "") { 164404fd306cSNickeau $attributes = $this->getViewBoxAttributes($viewBox); 164504fd306cSNickeau $viewBoxWidth = $attributes[2]; 164604fd306cSNickeau try { 164704fd306cSNickeau /** 164804fd306cSNickeau * Ceil because we want to see a border if there is one 164904fd306cSNickeau */ 165004fd306cSNickeau $this->intrinsicWidth = DataType::toIntegerCeil($viewBoxWidth); 165104fd306cSNickeau return $this; 165204fd306cSNickeau } catch (ExceptionCompile $e) { 165304fd306cSNickeau throw new ExceptionBadSyntax("The media with ($viewBoxWidth) of the svg image ($this) is not a valid integer value"); 165404fd306cSNickeau } 165504fd306cSNickeau } 165604fd306cSNickeau 165704fd306cSNickeau /** 165804fd306cSNickeau * Case with some icon such as 165904fd306cSNickeau * https://raw.githubusercontent.com/fefanto/fontaudio/master/svgs/fad-random-1dice.svg 166004fd306cSNickeau */ 166104fd306cSNickeau $width = $this->getXmlDom()->documentElement->getAttribute("width"); 166204fd306cSNickeau if ($width === "") { 166304fd306cSNickeau throw new ExceptionBadSyntax("The svg ($this) does not have a viewBox or width attribute, the intrinsic width cannot be determined"); 166404fd306cSNickeau } 166504fd306cSNickeau try { 166604fd306cSNickeau $this->intrinsicWidth = DataType::toInteger($width); 166704fd306cSNickeau return $this; 166804fd306cSNickeau } catch (ExceptionCompile $e) { 166904fd306cSNickeau throw new ExceptionBadSyntax("The media width ($width) of the svg image ($this) is not a valid integer value"); 167004fd306cSNickeau } 167104fd306cSNickeau } 167204fd306cSNickeau 167304fd306cSNickeau /** 167404fd306cSNickeau * Build is done late because we want to be able to create a fetch url even if the file is not a correct svg 167504fd306cSNickeau * 167604fd306cSNickeau * The downside is that there is an exception that may be triggered all over the place 167704fd306cSNickeau * 167804fd306cSNickeau * 167904fd306cSNickeau * @throws ExceptionBadSyntax 168004fd306cSNickeau */ 168104fd306cSNickeau private function buildXmlDocumentIfNeeded(string $markup = null): FetcherSvg 168204fd306cSNickeau { 168304fd306cSNickeau /** 168404fd306cSNickeau * The svg document may be build 168504fd306cSNickeau * via markup (See {@link self::setMarkup()} 168604fd306cSNickeau */ 168704fd306cSNickeau if ($this->xmlDocument !== null) { 168804fd306cSNickeau return $this; 168904fd306cSNickeau } 169004fd306cSNickeau 169104fd306cSNickeau /** 169204fd306cSNickeau * Markup string passed directly or 169304fd306cSNickeau * via the source path below 169404fd306cSNickeau */ 169504fd306cSNickeau if ($markup !== null) { 169604fd306cSNickeau $this->xmlDocument = XmlDocument::createXmlDocFromMarkup($markup); 169704fd306cSNickeau $localName = $this->xmlDocument->getElement()->getLocalName(); 169804fd306cSNickeau if ($localName !== "svg") { 169904fd306cSNickeau throw new ExceptionBadSyntax("This is not a svg but a $localName element."); 170004fd306cSNickeau } 170104fd306cSNickeau $this->setIntrinsicDimensions(); 170204fd306cSNickeau return $this; 170304fd306cSNickeau } 170404fd306cSNickeau 170504fd306cSNickeau /** 170604fd306cSNickeau * A svg path 170704fd306cSNickeau * 170804fd306cSNickeau * Because we test bad svg, we want to be able to build an url. 170904fd306cSNickeau * We don't want therefore to throw when the svg file is not valid 171004fd306cSNickeau * We therefore check the validity at runtime 171104fd306cSNickeau */ 171204fd306cSNickeau $path = $this->getSourcePath(); 171304fd306cSNickeau try { 171404fd306cSNickeau $markup = FileSystems::getContent($path); 171504fd306cSNickeau } catch (ExceptionNotFound $e) { 171604fd306cSNickeau throw new ExceptionRuntime("The svg file ($path) was not found", self::CANONICAL); 171704fd306cSNickeau } 171804fd306cSNickeau try { 171904fd306cSNickeau $this->buildXmlDocumentIfNeeded($markup); 172004fd306cSNickeau } catch (ExceptionBadSyntax $e) { 172104fd306cSNickeau throw new ExceptionRuntime("The svg file ($path) is not a valid svg. Error: {$e->getMessage()}"); 172204fd306cSNickeau } 172304fd306cSNickeau 172404fd306cSNickeau // dimension 172504fd306cSNickeau return $this; 172604fd306cSNickeau 172704fd306cSNickeau } 172804fd306cSNickeau 172904fd306cSNickeau /** 173004fd306cSNickeau * @return bool - true if the svg is an icon 173104fd306cSNickeau */ 173204fd306cSNickeau public function isIconStructure(): bool 173304fd306cSNickeau { 173404fd306cSNickeau return $this->getInternalStructureType() === self::ICON_TYPE; 173504fd306cSNickeau } 173604fd306cSNickeau 173704fd306cSNickeau /** 173804fd306cSNickeau * @return string - the internal structure of the svg 173904fd306cSNickeau * of {@link self::ICON_TYPE} or {@link self::ILLUSTRATION_TYPE} 174004fd306cSNickeau */ 174104fd306cSNickeau private function getInternalStructureType(): string 174204fd306cSNickeau { 174304fd306cSNickeau 174404fd306cSNickeau $mediaWidth = $this->getIntrinsicWidth(); 174504fd306cSNickeau $mediaHeight = $this->getIntrinsicHeight(); 174604fd306cSNickeau 174704fd306cSNickeau if ( 174804fd306cSNickeau $mediaWidth == $mediaHeight 174904fd306cSNickeau && $mediaWidth < 400) // 356 for logos telegram are the size of the twitter emoji but tile may be bigger ? 175004fd306cSNickeau { 175104fd306cSNickeau return FetcherSvg::ICON_TYPE; 175204fd306cSNickeau } else { 175304fd306cSNickeau $svgStructureType = FetcherSvg::ILLUSTRATION_TYPE; 175404fd306cSNickeau 175504fd306cSNickeau // some icon may be bigger 175604fd306cSNickeau // in size than 400. example 1024 for ant-design:table-outlined 175704fd306cSNickeau // https://github.com/ant-design/ant-design-icons/blob/master/packages/icons-svg/svg/outlined/table.svg 175804fd306cSNickeau // or not squared 175904fd306cSNickeau // if the usage is determined or the svg is in the icon directory, it just takes over. 176004fd306cSNickeau try { 176104fd306cSNickeau $isInIconDirectory = IconDownloader::isInIconDirectory($this->getSourcePath()); 176204fd306cSNickeau } catch (ExceptionNotFound $e) { 176304fd306cSNickeau // not a svg from a path 176404fd306cSNickeau $isInIconDirectory = false; 176504fd306cSNickeau } 176604fd306cSNickeau try { 176704fd306cSNickeau $requestType = $this->getRequestedType(); 176804fd306cSNickeau } catch (ExceptionNotFound $e) { 176904fd306cSNickeau $requestType = false; 177004fd306cSNickeau } 177104fd306cSNickeau 177204fd306cSNickeau if ($requestType === FetcherSvg::ICON_TYPE || $isInIconDirectory) { 177304fd306cSNickeau $svgStructureType = FetcherSvg::ICON_TYPE; 177404fd306cSNickeau } 177504fd306cSNickeau 177604fd306cSNickeau return $svgStructureType; 177704fd306cSNickeau 177804fd306cSNickeau } 177904fd306cSNickeau } 178004fd306cSNickeau 178104fd306cSNickeau /** 178204fd306cSNickeau * 178304fd306cSNickeau * This function returns a consistent requested width and height for icon and tile 178404fd306cSNickeau * 178504fd306cSNickeau * @throws ExceptionNotFound - if not a icon or tile requested 178604fd306cSNickeau */ 178704fd306cSNickeau private function getDefaultWidhtAndHeightForIconAndTileIfNotSet(): int 178804fd306cSNickeau { 178904fd306cSNickeau 179004fd306cSNickeau if (!$this->norWidthNorHeightWasRequested()) { 179104fd306cSNickeau throw new ExceptionNotFound(); 179204fd306cSNickeau } 179304fd306cSNickeau 179404fd306cSNickeau if ($this->isCropRequested()) { 179504fd306cSNickeau /** 179604fd306cSNickeau * With a crop, the internal dimension takes over 179704fd306cSNickeau */ 179804fd306cSNickeau throw new ExceptionNotFound(); 179904fd306cSNickeau } 180004fd306cSNickeau 180104fd306cSNickeau $internalStructure = $this->getInternalStructureType(); 180204fd306cSNickeau switch ($internalStructure) { 180304fd306cSNickeau case FetcherSvg::ICON_TYPE: 180404fd306cSNickeau try { 180504fd306cSNickeau $requestedType = $this->getRequestedType(); 180604fd306cSNickeau } catch (ExceptionNotFound $e) { 180704fd306cSNickeau $requestedType = FetcherSvg::ICON_TYPE; 180804fd306cSNickeau } 180904fd306cSNickeau switch ($requestedType) { 181004fd306cSNickeau case FetcherSvg::TILE_TYPE: 181104fd306cSNickeau return self::DEFAULT_TILE_WIDTH; 181204fd306cSNickeau default: 181304fd306cSNickeau case FetcherSvg::ICON_TYPE: 181404fd306cSNickeau return FetcherSvg::DEFAULT_ICON_LENGTH; 181504fd306cSNickeau } 181604fd306cSNickeau default: 181704fd306cSNickeau throw new ExceptionNotFound(); 181804fd306cSNickeau } 181904fd306cSNickeau 182004fd306cSNickeau 182104fd306cSNickeau } 182204fd306cSNickeau 182304fd306cSNickeau 182404fd306cSNickeau} 1825