1*04fd306cSNickeau<?php 2*04fd306cSNickeau 3*04fd306cSNickeau 4*04fd306cSNickeaunamespace ComboStrap; 5*04fd306cSNickeau 6*04fd306cSNickeauuse ComboStrap\TagAttribute\StyleAttribute; 7*04fd306cSNickeauuse ComboStrap\Web\Url; 8*04fd306cSNickeauuse ComboStrap\Xml\XmlDocument; 9*04fd306cSNickeauuse ComboStrap\Xml\XmlSystems; 10*04fd306cSNickeauuse DOMAttr; 11*04fd306cSNickeauuse DOMElement; 12*04fd306cSNickeauuse splitbrain\phpcli\Colors; 13*04fd306cSNickeau 14*04fd306cSNickeau/** 15*04fd306cSNickeau * Class ImageSvg 16*04fd306cSNickeau * @package ComboStrap 17*04fd306cSNickeau * 18*04fd306cSNickeau * Svg image fetch processing that can output: 19*04fd306cSNickeau * * an URL for an HTTP request 20*04fd306cSNickeau * * an SvgFile for an HTTP response or any further processing 21*04fd306cSNickeau * 22*04fd306cSNickeau * The original svg can be set with: 23*04fd306cSNickeau * * the {@link FetcherSvg::setSourcePath() original path} 24*04fd306cSNickeau * * 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} 25*04fd306cSNickeau * * or by {@link FetcherSvg::setMarkup() Svg Markup} 26*04fd306cSNickeau * 27*04fd306cSNickeau */ 28*04fd306cSNickeauclass FetcherSvg extends IFetcherLocalImage 29*04fd306cSNickeau{ 30*04fd306cSNickeau 31*04fd306cSNickeau use FetcherTraitWikiPath { 32*04fd306cSNickeau setSourcePath as protected setOriginalPathTraitAlias; 33*04fd306cSNickeau } 34*04fd306cSNickeau 35*04fd306cSNickeau const EXTENSION = "svg"; 36*04fd306cSNickeau const CANONICAL = "svg"; 37*04fd306cSNickeau 38*04fd306cSNickeau const REQUESTED_PRESERVE_ASPECT_RATIO_KEY = "preserveAspectRatio"; 39*04fd306cSNickeau public const CURRENT_COLOR = "currentColor"; 40*04fd306cSNickeau /** 41*04fd306cSNickeau * Default SVG values 42*04fd306cSNickeau * https://github.com/svg/svgo/blob/master/plugins/_collections.js#L1579 43*04fd306cSNickeau * The key are exact (not lowercase) to be able to look them up 44*04fd306cSNickeau * for optimization 45*04fd306cSNickeau */ 46*04fd306cSNickeau public const SVG_DEFAULT_ATTRIBUTES_VALUE = array( 47*04fd306cSNickeau "x" => '0', 48*04fd306cSNickeau "y" => '0', 49*04fd306cSNickeau "width" => '100%', 50*04fd306cSNickeau "height" => '100%', 51*04fd306cSNickeau "preserveAspectRatio" => 'xMidYMid meet', 52*04fd306cSNickeau "zoomAndPan" => 'magnify', 53*04fd306cSNickeau "version" => '1.1', 54*04fd306cSNickeau "baseProfile" => 'none', 55*04fd306cSNickeau "contentScriptType" => 'application/ecmascript', 56*04fd306cSNickeau "contentStyleType" => 'text/css', 57*04fd306cSNickeau ); 58*04fd306cSNickeau /** 59*04fd306cSNickeau * The namespace of the editors 60*04fd306cSNickeau * https://github.com/svg/svgo/blob/master/plugins/_collections.js#L1841 61*04fd306cSNickeau */ 62*04fd306cSNickeau public const EDITOR_NAMESPACE = [ 63*04fd306cSNickeau 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', 64*04fd306cSNickeau 'http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd', 65*04fd306cSNickeau 'http://www.inkscape.org/namespaces/inkscape', 66*04fd306cSNickeau 'http://www.bohemiancoding.com/sketch/ns', 67*04fd306cSNickeau 'http://ns.adobe.com/AdobeIllustrator/10.0/', 68*04fd306cSNickeau 'http://ns.adobe.com/Graphs/1.0/', 69*04fd306cSNickeau 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/', 70*04fd306cSNickeau 'http://ns.adobe.com/Variables/1.0/', 71*04fd306cSNickeau 'http://ns.adobe.com/SaveForWeb/1.0/', 72*04fd306cSNickeau 'http://ns.adobe.com/Extensibility/1.0/', 73*04fd306cSNickeau 'http://ns.adobe.com/Flows/1.0/', 74*04fd306cSNickeau 'http://ns.adobe.com/ImageReplacement/1.0/', 75*04fd306cSNickeau 'http://ns.adobe.com/GenericCustomNamespace/1.0/', 76*04fd306cSNickeau 'http://ns.adobe.com/XPath/1.0/', 77*04fd306cSNickeau 'http://schemas.microsoft.com/visio/2003/SVGExtensions/', 78*04fd306cSNickeau 'http://taptrix.com/vectorillustrator/svg_extensions', 79*04fd306cSNickeau 'http://www.figma.com/figma/ns', 80*04fd306cSNickeau 'http://purl.org/dc/elements/1.1/', 81*04fd306cSNickeau 'http://creativecommons.org/ns#', 82*04fd306cSNickeau 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 83*04fd306cSNickeau 'http://www.serif.com/', 84*04fd306cSNickeau 'http://www.vector.evaxdesign.sk', 85*04fd306cSNickeau ]; 86*04fd306cSNickeau public const CONF_PRESERVE_ASPECT_RATIO_DEFAULT = "svgPreserveAspectRatioDefault"; 87*04fd306cSNickeau public const TILE_TYPE = "tile"; 88*04fd306cSNickeau public const CONF_OPTIMIZATION_ELEMENTS_TO_DELETE = "svgOptimizationElementsToDelete"; 89*04fd306cSNickeau public const VIEW_BOX = "viewBox"; 90*04fd306cSNickeau /** 91*04fd306cSNickeau * Optimization Configuration 92*04fd306cSNickeau */ 93*04fd306cSNickeau public const CONF_OPTIMIZATION_NAMESPACES_TO_KEEP = "svgOptimizationNamespacesToKeep"; 94*04fd306cSNickeau public const CONF_SVG_OPTIMIZATION_ENABLE = "svgOptimizationEnable"; 95*04fd306cSNickeau public const COLOR_TYPE_STROKE_OUTLINE = FetcherSvg::STROKE_ATTRIBUTE; 96*04fd306cSNickeau public const CONF_OPTIMIZATION_ATTRIBUTES_TO_DELETE = "svgOptimizationAttributesToDelete"; 97*04fd306cSNickeau public const CONF_OPTIMIZATION_ELEMENTS_TO_DELETE_IF_EMPTY = "svgOptimizationElementsToDeleteIfEmpty"; 98*04fd306cSNickeau public const SVG_NAMESPACE_URI = "http://www.w3.org/2000/svg"; 99*04fd306cSNickeau public const STROKE_ATTRIBUTE = "stroke"; 100*04fd306cSNickeau public const DEFAULT_ICON_LENGTH = 24; 101*04fd306cSNickeau public const REQUESTED_NAME_ATTRIBUTE = "name"; 102*04fd306cSNickeau public const REQUESTED_PRESERVE_ATTRIBUTE = "preserve"; 103*04fd306cSNickeau public const ILLUSTRATION_TYPE = "illustration"; 104*04fd306cSNickeau /** 105*04fd306cSNickeau * There is only two type of svg icon / tile 106*04fd306cSNickeau * * fill color is on the surface (known also as Solid) 107*04fd306cSNickeau * * stroke, the color is on the path (known as Outline 108*04fd306cSNickeau */ 109*04fd306cSNickeau public const COLOR_TYPE_FILL_SOLID = "fill"; 110*04fd306cSNickeau /** 111*04fd306cSNickeau * Type of svg 112*04fd306cSNickeau * * Icon and tile have the same characteristic (ie viewbox = 0 0 A A) and the color can be set) 113*04fd306cSNickeau * * An illustration does not have rectangle shape and the color is not set 114*04fd306cSNickeau */ 115*04fd306cSNickeau public const ICON_TYPE = "icon"; 116*04fd306cSNickeau /** 117*04fd306cSNickeau * Namespace (used to query with xpath only the svg node) 118*04fd306cSNickeau */ 119*04fd306cSNickeau public const SVG_NAMESPACE_PREFIX = "svg"; 120*04fd306cSNickeau const TAG = "svg"; 121*04fd306cSNickeau public const NAME_ATTRIBUTE = "name"; 122*04fd306cSNickeau public const DATA_NAME_HTML_ATTRIBUTE = "data-name"; 123*04fd306cSNickeau const DEFAULT_TILE_WIDTH = 192; 124*04fd306cSNickeau 125*04fd306cSNickeau 126*04fd306cSNickeau private ?ColorRgb $color = null; 127*04fd306cSNickeau private ?string $preserveAspectRatio = null; 128*04fd306cSNickeau private ?bool $preserveStyle = null; 129*04fd306cSNickeau private ?string $requestedType = null; 130*04fd306cSNickeau private bool $processed = false; 131*04fd306cSNickeau private ?float $zoomFactor = null; 132*04fd306cSNickeau private ?string $requestedClass = null; 133*04fd306cSNickeau private int $intrinsicHeight; 134*04fd306cSNickeau private int $intrinsicWidth; 135*04fd306cSNickeau private string $name; 136*04fd306cSNickeau 137*04fd306cSNickeau 138*04fd306cSNickeau private static function createSvgEmpty(): FetcherSvg 139*04fd306cSNickeau { 140*04fd306cSNickeau return new FetcherSvg(); 141*04fd306cSNickeau } 142*04fd306cSNickeau 143*04fd306cSNickeau /** 144*04fd306cSNickeau */ 145*04fd306cSNickeau public static function createSvgFromPath(WikiPath $path): FetcherSvg 146*04fd306cSNickeau { 147*04fd306cSNickeau $fetcher = self::createSvgEmpty(); 148*04fd306cSNickeau 149*04fd306cSNickeau $fetcher->setSourcePath($path); 150*04fd306cSNickeau return $fetcher; 151*04fd306cSNickeau } 152*04fd306cSNickeau 153*04fd306cSNickeau /** 154*04fd306cSNickeau * @throws ExceptionBadArgument 155*04fd306cSNickeau */ 156*04fd306cSNickeau public static function createSvgFromFetchUrl(Url $fetchUrl): FetcherSvg 157*04fd306cSNickeau { 158*04fd306cSNickeau $fetchSvg = self::createSvgEmpty(); 159*04fd306cSNickeau $fetchSvg->buildFromUrl($fetchUrl); 160*04fd306cSNickeau return $fetchSvg; 161*04fd306cSNickeau } 162*04fd306cSNickeau 163*04fd306cSNickeau /** 164*04fd306cSNickeau * @param string $markup - the svg as a string 165*04fd306cSNickeau * @param string $name - a name identifier (used in diff) 166*04fd306cSNickeau * @return FetcherSvg 167*04fd306cSNickeau * @throws ExceptionBadSyntax 168*04fd306cSNickeau */ 169*04fd306cSNickeau public static function createSvgFromMarkup(string $markup, string $name): FetcherSvg 170*04fd306cSNickeau { 171*04fd306cSNickeau return self::createSvgEmpty()->setMarkup($markup, $name); 172*04fd306cSNickeau } 173*04fd306cSNickeau 174*04fd306cSNickeau /** 175*04fd306cSNickeau * @param TagAttributes $tagAttributes 176*04fd306cSNickeau * @return FetcherSvg 177*04fd306cSNickeau * @throws ExceptionBadArgument 178*04fd306cSNickeau * @throws ExceptionBadSyntax 179*04fd306cSNickeau * @throws ExceptionCompile 180*04fd306cSNickeau */ 181*04fd306cSNickeau public static function createFromAttributes(TagAttributes $tagAttributes): FetcherSvg 182*04fd306cSNickeau { 183*04fd306cSNickeau $fetcher = FetcherSvg::createSvgEmpty(); 184*04fd306cSNickeau $fetcher->buildFromTagAttributes($tagAttributes); 185*04fd306cSNickeau return $fetcher; 186*04fd306cSNickeau } 187*04fd306cSNickeau 188*04fd306cSNickeau /** 189*04fd306cSNickeau * @throws ExceptionNotFound 190*04fd306cSNickeau */ 191*04fd306cSNickeau public function getRequestedOptimization(): bool 192*04fd306cSNickeau { 193*04fd306cSNickeau 194*04fd306cSNickeau if ($this->requestedOptimization === null) { 195*04fd306cSNickeau throw new ExceptionNotFound("Optimization was not set"); 196*04fd306cSNickeau } 197*04fd306cSNickeau return $this->requestedOptimization; 198*04fd306cSNickeau 199*04fd306cSNickeau } 200*04fd306cSNickeau 201*04fd306cSNickeau public function getRequestedOptimizeOrDefault(): bool 202*04fd306cSNickeau { 203*04fd306cSNickeau try { 204*04fd306cSNickeau return $this->getRequestedOptimization(); 205*04fd306cSNickeau } catch (ExceptionNotFound $e) { 206*04fd306cSNickeau return SiteConfig::getConfValue(FetcherSvg::CONF_SVG_OPTIMIZATION_ENABLE, 1); 207*04fd306cSNickeau } 208*04fd306cSNickeau 209*04fd306cSNickeau } 210*04fd306cSNickeau 211*04fd306cSNickeau /** 212*04fd306cSNickeau * @throws ExceptionNotFound 213*04fd306cSNickeau */ 214*04fd306cSNickeau public function getRequestedPreserveStyle(): bool 215*04fd306cSNickeau { 216*04fd306cSNickeau 217*04fd306cSNickeau if ($this->preserveStyle === null) { 218*04fd306cSNickeau throw new ExceptionNotFound("No preserve style attribute was set"); 219*04fd306cSNickeau } 220*04fd306cSNickeau return $this->preserveStyle; 221*04fd306cSNickeau 222*04fd306cSNickeau } 223*04fd306cSNickeau 224*04fd306cSNickeau 225*04fd306cSNickeau /** 226*04fd306cSNickeau * @param $boolean 227*04fd306cSNickeau * @return FetcherSvg 228*04fd306cSNickeau */ 229*04fd306cSNickeau public function setRequestedOptimization($boolean): FetcherSvg 230*04fd306cSNickeau { 231*04fd306cSNickeau $this->requestedOptimization = $boolean; 232*04fd306cSNickeau return $this; 233*04fd306cSNickeau } 234*04fd306cSNickeau 235*04fd306cSNickeau /** 236*04fd306cSNickeau * Optimization 237*04fd306cSNickeau * Based on https://jakearchibald.github.io/svgomg/ 238*04fd306cSNickeau * (gui of https://github.com/svg/svgo) 239*04fd306cSNickeau * 240*04fd306cSNickeau * @throws ExceptionBadSyntax 241*04fd306cSNickeau * @throws ExceptionBadState 242*04fd306cSNickeau */ 243*04fd306cSNickeau public 244*04fd306cSNickeau function optimize() 245*04fd306cSNickeau { 246*04fd306cSNickeau 247*04fd306cSNickeau if ($this->getRequestedOptimizeOrDefault()) { 248*04fd306cSNickeau 249*04fd306cSNickeau /** 250*04fd306cSNickeau * Delete Editor namespace 251*04fd306cSNickeau * https://github.com/svg/svgo/blob/master/plugins/removeEditorsNSData.js 252*04fd306cSNickeau */ 253*04fd306cSNickeau $confNamespaceToKeeps = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_NAMESPACES_TO_KEEP); 254*04fd306cSNickeau $namespaceToKeep = StringUtility::explodeAndTrim($confNamespaceToKeeps, ","); 255*04fd306cSNickeau foreach ($this->getXmlDocument()->getNamespaces() as $namespacePrefix => $namespaceUri) { 256*04fd306cSNickeau if ( 257*04fd306cSNickeau !empty($namespacePrefix) 258*04fd306cSNickeau && $namespacePrefix != "svg" 259*04fd306cSNickeau && !in_array($namespacePrefix, $namespaceToKeep) 260*04fd306cSNickeau && in_array($namespaceUri, FetcherSvg::EDITOR_NAMESPACE) 261*04fd306cSNickeau ) { 262*04fd306cSNickeau $this->getXmlDocument()->removeNamespace($namespaceUri); 263*04fd306cSNickeau } 264*04fd306cSNickeau } 265*04fd306cSNickeau 266*04fd306cSNickeau /** 267*04fd306cSNickeau * Delete empty namespace rules 268*04fd306cSNickeau */ 269*04fd306cSNickeau $documentElement = $this->getXmlDocument()->getDomDocument()->documentElement; 270*04fd306cSNickeau foreach ($this->getXmlDocument()->getNamespaces() as $namespacePrefix => $namespaceUri) { 271*04fd306cSNickeau $nodes = $this->getXmlDocument()->xpath("//*[namespace-uri()='$namespaceUri']"); 272*04fd306cSNickeau $attributes = $this->getXmlDocument()->xpath("//@*[namespace-uri()='$namespaceUri']"); 273*04fd306cSNickeau if ($nodes->length == 0 && $attributes->length == 0) { 274*04fd306cSNickeau $result = $documentElement->removeAttributeNS($namespaceUri, $namespacePrefix); 275*04fd306cSNickeau if ($result === false) { 276*04fd306cSNickeau LogUtility::msg("Internal error: The deletion of the empty namespace ($namespacePrefix:$namespaceUri) didn't succeed", LogUtility::LVL_MSG_WARNING, "support"); 277*04fd306cSNickeau } 278*04fd306cSNickeau } 279*04fd306cSNickeau } 280*04fd306cSNickeau 281*04fd306cSNickeau /** 282*04fd306cSNickeau * Delete comments 283*04fd306cSNickeau */ 284*04fd306cSNickeau $commentNodes = $this->getXmlDocument()->xpath("//comment()"); 285*04fd306cSNickeau foreach ($commentNodes as $commentNode) { 286*04fd306cSNickeau $this->getXmlDocument()->removeNode($commentNode); 287*04fd306cSNickeau } 288*04fd306cSNickeau 289*04fd306cSNickeau /** 290*04fd306cSNickeau * Delete default value (version=1.1 for instance) 291*04fd306cSNickeau */ 292*04fd306cSNickeau $defaultValues = FetcherSvg::SVG_DEFAULT_ATTRIBUTES_VALUE; 293*04fd306cSNickeau foreach ($documentElement->attributes as $attribute) { 294*04fd306cSNickeau /** @var DOMAttr $attribute */ 295*04fd306cSNickeau $name = $attribute->name; 296*04fd306cSNickeau if (isset($defaultValues[$name])) { 297*04fd306cSNickeau if ($defaultValues[$name] == $attribute->value) { 298*04fd306cSNickeau $documentElement->removeAttributeNode($attribute); 299*04fd306cSNickeau } 300*04fd306cSNickeau } 301*04fd306cSNickeau } 302*04fd306cSNickeau 303*04fd306cSNickeau /** 304*04fd306cSNickeau * Suppress the attributes (by default id, style and class, data-name) 305*04fd306cSNickeau */ 306*04fd306cSNickeau $attributeConfToDelete = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_ATTRIBUTES_TO_DELETE, "id, style, class, data-name"); 307*04fd306cSNickeau $attributesNameToDelete = StringUtility::explodeAndTrim($attributeConfToDelete, ","); 308*04fd306cSNickeau foreach ($attributesNameToDelete as $value) { 309*04fd306cSNickeau 310*04fd306cSNickeau if (in_array($value, ["style", "class", "id"]) && $this->getRequestedPreserveStyleOrDefault()) { 311*04fd306cSNickeau // we preserve the style, we preserve the class 312*04fd306cSNickeau continue; 313*04fd306cSNickeau } 314*04fd306cSNickeau 315*04fd306cSNickeau $nodes = $this->getXmlDocument()->xpath("//@$value"); 316*04fd306cSNickeau foreach ($nodes as $node) { 317*04fd306cSNickeau /** @var DOMAttr $node */ 318*04fd306cSNickeau /** @var DOMElement $DOMNode */ 319*04fd306cSNickeau $DOMNode = $node->parentNode; 320*04fd306cSNickeau $DOMNode->removeAttributeNode($node); 321*04fd306cSNickeau } 322*04fd306cSNickeau } 323*04fd306cSNickeau 324*04fd306cSNickeau /** 325*04fd306cSNickeau * Remove width/height that coincides with a viewBox attr 326*04fd306cSNickeau * https://www.w3.org/TR/SVG11/coords.html#ViewBoxAttribute 327*04fd306cSNickeau * Example: 328*04fd306cSNickeau * <svg width="100" height="50" viewBox="0 0 100 50"> 329*04fd306cSNickeau * <svg viewBox="0 0 100 50"> 330*04fd306cSNickeau * 331*04fd306cSNickeau */ 332*04fd306cSNickeau $widthAttributeValue = $documentElement->getAttribute("width"); 333*04fd306cSNickeau if (!empty($widthAttributeValue)) { 334*04fd306cSNickeau $widthPixel = Unit::toPixel($widthAttributeValue); 335*04fd306cSNickeau 336*04fd306cSNickeau $heightAttributeValue = $documentElement->getAttribute("height"); 337*04fd306cSNickeau if (!empty($heightAttributeValue)) { 338*04fd306cSNickeau $heightPixel = Unit::toPixel($heightAttributeValue); 339*04fd306cSNickeau 340*04fd306cSNickeau // ViewBox 341*04fd306cSNickeau $viewBoxAttribute = $documentElement->getAttribute(FetcherSvg::VIEW_BOX); 342*04fd306cSNickeau if (!empty($viewBoxAttribute)) { 343*04fd306cSNickeau $viewBoxAttributeAsArray = StringUtility::explodeAndTrim($viewBoxAttribute, " "); 344*04fd306cSNickeau 345*04fd306cSNickeau if (sizeof($viewBoxAttributeAsArray) == 4) { 346*04fd306cSNickeau $minX = $viewBoxAttributeAsArray[0]; 347*04fd306cSNickeau $minY = $viewBoxAttributeAsArray[1]; 348*04fd306cSNickeau $widthViewPort = $viewBoxAttributeAsArray[2]; 349*04fd306cSNickeau $heightViewPort = $viewBoxAttributeAsArray[3]; 350*04fd306cSNickeau if ( 351*04fd306cSNickeau $minX == 0 & 352*04fd306cSNickeau $minY == 0 & 353*04fd306cSNickeau $widthViewPort == $widthPixel & 354*04fd306cSNickeau $heightViewPort == $heightPixel 355*04fd306cSNickeau ) { 356*04fd306cSNickeau $documentElement->removeAttribute("width"); 357*04fd306cSNickeau $documentElement->removeAttribute("height"); 358*04fd306cSNickeau } 359*04fd306cSNickeau 360*04fd306cSNickeau } 361*04fd306cSNickeau } 362*04fd306cSNickeau } 363*04fd306cSNickeau } 364*04fd306cSNickeau 365*04fd306cSNickeau 366*04fd306cSNickeau /** 367*04fd306cSNickeau * Suppress script and style 368*04fd306cSNickeau * 369*04fd306cSNickeau * 370*04fd306cSNickeau * Delete of scripts https://developer.mozilla.org/en-US/docs/Web/SVG/Element/script 371*04fd306cSNickeau * 372*04fd306cSNickeau * And defs/style 373*04fd306cSNickeau * 374*04fd306cSNickeau * The style can leak in other icon/svg inlined in the document 375*04fd306cSNickeau * 376*04fd306cSNickeau * Technically on icon, there should be no `style` 377*04fd306cSNickeau * on inline icon otherwise, the css style can leak 378*04fd306cSNickeau * 379*04fd306cSNickeau * Example with carbon that use cls-1 on all icons 380*04fd306cSNickeau * https://github.com/carbon-design-system/carbon/issues/5568 381*04fd306cSNickeau * The facebook icon has a class cls-1 with an opacity of 0 382*04fd306cSNickeau * that leaks to the tumblr icon that has also a cls-1 class 383*04fd306cSNickeau * 384*04fd306cSNickeau * The illustration uses inline fill to color and styled 385*04fd306cSNickeau * For instance, all un-draw: https://undraw.co/illustrations 386*04fd306cSNickeau */ 387*04fd306cSNickeau $elementsToDeleteConf = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_ELEMENTS_TO_DELETE, "script, style, title, desc"); 388*04fd306cSNickeau $elementsToDelete = StringUtility::explodeAndTrim($elementsToDeleteConf, ","); 389*04fd306cSNickeau foreach ($elementsToDelete as $elementToDelete) { 390*04fd306cSNickeau if ($elementToDelete === "style" && $this->getRequestedPreserveStyleOrDefault()) { 391*04fd306cSNickeau continue; 392*04fd306cSNickeau } 393*04fd306cSNickeau XmlSystems::deleteAllElementsByName($elementToDelete, $this->getXmlDocument()); 394*04fd306cSNickeau } 395*04fd306cSNickeau 396*04fd306cSNickeau // Delete If Empty 397*04fd306cSNickeau // * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs 398*04fd306cSNickeau // * https://developer.mozilla.org/en-US/docs/Web/SVG/Element/metadata 399*04fd306cSNickeau $elementsToDeleteIfEmptyConf = SiteConfig::getConfValue(FetcherSvg::CONF_OPTIMIZATION_ELEMENTS_TO_DELETE_IF_EMPTY, "metadata, defs, g"); 400*04fd306cSNickeau $elementsToDeleteIfEmpty = StringUtility::explodeAndTrim($elementsToDeleteIfEmptyConf); 401*04fd306cSNickeau foreach ($elementsToDeleteIfEmpty as $elementToDeleteIfEmpty) { 402*04fd306cSNickeau $elementNodeList = $this->getXmlDocument()->xpath("//*[local-name()='$elementToDeleteIfEmpty']"); 403*04fd306cSNickeau foreach ($elementNodeList as $element) { 404*04fd306cSNickeau /** @var DOMElement $element */ 405*04fd306cSNickeau if (!$element->hasChildNodes()) { 406*04fd306cSNickeau $element->parentNode->removeChild($element); 407*04fd306cSNickeau } 408*04fd306cSNickeau } 409*04fd306cSNickeau } 410*04fd306cSNickeau 411*04fd306cSNickeau /** 412*04fd306cSNickeau * Delete the svg prefix namespace definition 413*04fd306cSNickeau * At the end to be able to query with svg as prefix 414*04fd306cSNickeau */ 415*04fd306cSNickeau if (!in_array("svg", $namespaceToKeep)) { 416*04fd306cSNickeau $documentElement->removeAttributeNS(FetcherSvg::SVG_NAMESPACE_URI, FetcherSvg::SVG_NAMESPACE_PREFIX); 417*04fd306cSNickeau } 418*04fd306cSNickeau 419*04fd306cSNickeau } 420*04fd306cSNickeau } 421*04fd306cSNickeau 422*04fd306cSNickeau 423*04fd306cSNickeau /** 424*04fd306cSNickeau * The height of the viewbox 425*04fd306cSNickeau * @return int 426*04fd306cSNickeau */ 427*04fd306cSNickeau public function getIntrinsicHeight(): int 428*04fd306cSNickeau { 429*04fd306cSNickeau 430*04fd306cSNickeau try { 431*04fd306cSNickeau $this->buildXmlDocumentIfNeeded(); 432*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 433*04fd306cSNickeau throw new ExceptionBadSyntaxRuntime($e->getMessage(), self::CANONICAL, 1, $e); 434*04fd306cSNickeau } 435*04fd306cSNickeau return $this->intrinsicHeight; 436*04fd306cSNickeau 437*04fd306cSNickeau } 438*04fd306cSNickeau 439*04fd306cSNickeau /** 440*04fd306cSNickeau * The width of the view box 441*04fd306cSNickeau * @return int 442*04fd306cSNickeau */ 443*04fd306cSNickeau public 444*04fd306cSNickeau function getIntrinsicWidth(): int 445*04fd306cSNickeau { 446*04fd306cSNickeau try { 447*04fd306cSNickeau $this->buildXmlDocumentIfNeeded(); 448*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 449*04fd306cSNickeau throw new ExceptionBadSyntaxRuntime($e->getMessage(), self::CANONICAL, 1, $e); 450*04fd306cSNickeau } 451*04fd306cSNickeau return $this->intrinsicWidth; 452*04fd306cSNickeau } 453*04fd306cSNickeau 454*04fd306cSNickeau /** 455*04fd306cSNickeau * @return string 456*04fd306cSNickeau * @throws ExceptionBadArgument 457*04fd306cSNickeau * @throws ExceptionBadState 458*04fd306cSNickeau * @throws ExceptionBadSyntax 459*04fd306cSNickeau * @throws ExceptionCompile 460*04fd306cSNickeau * @throws ExceptionNotFound 461*04fd306cSNickeau */ 462*04fd306cSNickeau public function processAndGetMarkup(): string 463*04fd306cSNickeau { 464*04fd306cSNickeau 465*04fd306cSNickeau return $this->process()->getMarkup(); 466*04fd306cSNickeau 467*04fd306cSNickeau 468*04fd306cSNickeau } 469*04fd306cSNickeau 470*04fd306cSNickeau 471*04fd306cSNickeau /** 472*04fd306cSNickeau * @throws ExceptionBadState - if no svg was set to be processed 473*04fd306cSNickeau */ 474*04fd306cSNickeau public function getMarkup(): string 475*04fd306cSNickeau { 476*04fd306cSNickeau return $this->getXmlDocument()->toXml(); 477*04fd306cSNickeau } 478*04fd306cSNickeau 479*04fd306cSNickeau /** 480*04fd306cSNickeau * @throws ExceptionBadSyntax 481*04fd306cSNickeau * @throws ExceptionNotFound 482*04fd306cSNickeau */ 483*04fd306cSNickeau public function setSourcePath(WikiPath $path): IFetcherLocalImage 484*04fd306cSNickeau { 485*04fd306cSNickeau 486*04fd306cSNickeau $this->setOriginalPathTraitAlias($path); 487*04fd306cSNickeau return $this; 488*04fd306cSNickeau 489*04fd306cSNickeau } 490*04fd306cSNickeau 491*04fd306cSNickeau 492*04fd306cSNickeau /** 493*04fd306cSNickeau * 494*04fd306cSNickeau * @return Url - the fetch url 495*04fd306cSNickeau * 496*04fd306cSNickeau */ 497*04fd306cSNickeau public function getFetchUrl(Url $url = null): Url 498*04fd306cSNickeau { 499*04fd306cSNickeau 500*04fd306cSNickeau $url = parent::getFetchUrl($url); 501*04fd306cSNickeau 502*04fd306cSNickeau /** 503*04fd306cSNickeau * Trait 504*04fd306cSNickeau */ 505*04fd306cSNickeau $this->addLocalPathParametersToFetchUrl($url, self::$MEDIA_QUERY_PARAMETER); 506*04fd306cSNickeau 507*04fd306cSNickeau /** 508*04fd306cSNickeau * Specific properties 509*04fd306cSNickeau */ 510*04fd306cSNickeau try { 511*04fd306cSNickeau $url->addQueryParameter(ColorRgb::COLOR, $this->getRequestedColor()->toCssValue()); 512*04fd306cSNickeau } catch (ExceptionNotFound $e) { 513*04fd306cSNickeau // no color ok 514*04fd306cSNickeau } 515*04fd306cSNickeau try { 516*04fd306cSNickeau $url->addQueryParameter(self::REQUESTED_PRESERVE_ASPECT_RATIO_KEY, $this->getRequestedPreserveAspectRatio()); 517*04fd306cSNickeau } catch (ExceptionNotFound $e) { 518*04fd306cSNickeau // no preserve ratio ok 519*04fd306cSNickeau } 520*04fd306cSNickeau try { 521*04fd306cSNickeau $url->addQueryParameter(self::REQUESTED_NAME_ATTRIBUTE, $this->getRequestedName()); 522*04fd306cSNickeau } catch (ExceptionNotFound $e) { 523*04fd306cSNickeau // no name 524*04fd306cSNickeau } 525*04fd306cSNickeau try { 526*04fd306cSNickeau $url->addQueryParameter(Dimension::ZOOM_ATTRIBUTE, $this->getRequestedZoom()); 527*04fd306cSNickeau } catch (ExceptionNotFound $e) { 528*04fd306cSNickeau // no name 529*04fd306cSNickeau } 530*04fd306cSNickeau try { 531*04fd306cSNickeau $url->addQueryParameter(TagAttributes::CLASS_KEY, $this->getRequestedClass()); 532*04fd306cSNickeau } catch (ExceptionNotFound $e) { 533*04fd306cSNickeau // no name 534*04fd306cSNickeau } 535*04fd306cSNickeau try { 536*04fd306cSNickeau $url->addQueryParameter(TagAttributes::TYPE_KEY, $this->getRequestedType()); 537*04fd306cSNickeau } catch (ExceptionNotFound $e) { 538*04fd306cSNickeau // no name 539*04fd306cSNickeau } 540*04fd306cSNickeau 541*04fd306cSNickeau return $url; 542*04fd306cSNickeau 543*04fd306cSNickeau } 544*04fd306cSNickeau 545*04fd306cSNickeau /** 546*04fd306cSNickeau * @throws ExceptionNotFound 547*04fd306cSNickeau */ 548*04fd306cSNickeau public function getRequestedPreserveAspectRatio(): string 549*04fd306cSNickeau { 550*04fd306cSNickeau if ($this->preserveAspectRatio === null) { 551*04fd306cSNickeau throw new ExceptionNotFound("No preserve Aspect Ratio was requested"); 552*04fd306cSNickeau } 553*04fd306cSNickeau return $this->preserveAspectRatio; 554*04fd306cSNickeau } 555*04fd306cSNickeau 556*04fd306cSNickeau /** 557*04fd306cSNickeau * Return the svg file transformed by the attributes 558*04fd306cSNickeau * from cache if possible. Used when making a fetch with the URL 559*04fd306cSNickeau * @return LocalPath 560*04fd306cSNickeau * @throws ExceptionBadArgument 561*04fd306cSNickeau * @throws ExceptionBadState 562*04fd306cSNickeau * @throws ExceptionBadSyntax - the file is not a svg file 563*04fd306cSNickeau * @throws ExceptionCompile 564*04fd306cSNickeau * @throws ExceptionNotFound - the file was not found 565*04fd306cSNickeau */ 566*04fd306cSNickeau public function getFetchPath(): LocalPath 567*04fd306cSNickeau { 568*04fd306cSNickeau 569*04fd306cSNickeau /** 570*04fd306cSNickeau * Generated svg file cache init 571*04fd306cSNickeau */ 572*04fd306cSNickeau $fetchCache = FetcherCache::createFrom($this); 573*04fd306cSNickeau $files[] = $this->getSourcePath(); 574*04fd306cSNickeau try { 575*04fd306cSNickeau $files[] = ClassUtility::getClassPath(FetcherSvg::class); 576*04fd306cSNickeau } catch (\ReflectionException $e) { 577*04fd306cSNickeau LogUtility::internalError("Unable to add the FetchImageSvg class as dependency. Error: {$e->getMessage()}"); 578*04fd306cSNickeau } 579*04fd306cSNickeau try { 580*04fd306cSNickeau $files[] = ClassUtility::getClassPath(XmlDocument::class); 581*04fd306cSNickeau } catch (\ReflectionException $e) { 582*04fd306cSNickeau LogUtility::internalError("Unable to add the XmlDocument class as dependency. Error: {$e->getMessage()}"); 583*04fd306cSNickeau } 584*04fd306cSNickeau $files = array_merge(Site::getConfigurationFiles(), $files); // svg generation depends on configuration 585*04fd306cSNickeau foreach ($files as $file) { 586*04fd306cSNickeau $fetchCache->addFileDependency($file); 587*04fd306cSNickeau } 588*04fd306cSNickeau 589*04fd306cSNickeau global $ACT; 590*04fd306cSNickeau if (PluginUtility::isDev() && $ACT === ExecutionContext::PREVIEW_ACTION) { 591*04fd306cSNickeau // in dev mode, don't cache 592*04fd306cSNickeau $isCacheUsable = false; 593*04fd306cSNickeau } else { 594*04fd306cSNickeau $isCacheUsable = $fetchCache->isCacheUsable(); 595*04fd306cSNickeau } 596*04fd306cSNickeau if (!$isCacheUsable) { 597*04fd306cSNickeau $content = self::processAndGetMarkup(); 598*04fd306cSNickeau $fetchCache->storeCache($content); 599*04fd306cSNickeau } 600*04fd306cSNickeau return $fetchCache->getFile(); 601*04fd306cSNickeau 602*04fd306cSNickeau } 603*04fd306cSNickeau 604*04fd306cSNickeau /** 605*04fd306cSNickeau * The buster is also based on the configuration file 606*04fd306cSNickeau * 607*04fd306cSNickeau * It the user changes the configuration, the svg file is generated 608*04fd306cSNickeau * again and the browser cache should be deleted (ie the buster regenerated) 609*04fd306cSNickeau * 610*04fd306cSNickeau * {@link ResourceCombo::getBuster()} 611*04fd306cSNickeau * @return string 612*04fd306cSNickeau * 613*04fd306cSNickeau * @throws ExceptionNotFound 614*04fd306cSNickeau */ 615*04fd306cSNickeau public function getBuster(): string 616*04fd306cSNickeau { 617*04fd306cSNickeau $buster = FileSystems::getCacheBuster($this->getSourcePath()); 618*04fd306cSNickeau try { 619*04fd306cSNickeau $configFile = FileSystems::getCacheBuster(DirectoryLayout::getConfLocalFilePath()); 620*04fd306cSNickeau $buster = "$buster-$configFile"; 621*04fd306cSNickeau } catch (ExceptionNotFound $e) { 622*04fd306cSNickeau // no local conf file 623*04fd306cSNickeau if (PluginUtility::isDevOrTest()) { 624*04fd306cSNickeau LogUtility::internalError("A local configuration file should be present in dev"); 625*04fd306cSNickeau } 626*04fd306cSNickeau } 627*04fd306cSNickeau return $buster; 628*04fd306cSNickeau 629*04fd306cSNickeau } 630*04fd306cSNickeau 631*04fd306cSNickeau 632*04fd306cSNickeau function acceptsFetchUrl(Url $url): bool 633*04fd306cSNickeau { 634*04fd306cSNickeau 635*04fd306cSNickeau try { 636*04fd306cSNickeau $dokuPath = FetcherRawLocalPath::createEmpty()->buildFromUrl($url)->processIfNeededAndGetFetchPath(); 637*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 638*04fd306cSNickeau return false; 639*04fd306cSNickeau } 640*04fd306cSNickeau try { 641*04fd306cSNickeau $mime = FileSystems::getMime($dokuPath); 642*04fd306cSNickeau } catch (ExceptionNotFound $e) { 643*04fd306cSNickeau return false; 644*04fd306cSNickeau } 645*04fd306cSNickeau if ($mime->toString() === Mime::SVG) { 646*04fd306cSNickeau return true; 647*04fd306cSNickeau } 648*04fd306cSNickeau return false; 649*04fd306cSNickeau 650*04fd306cSNickeau } 651*04fd306cSNickeau 652*04fd306cSNickeau public function getMime(): Mime 653*04fd306cSNickeau { 654*04fd306cSNickeau return Mime::create(Mime::SVG); 655*04fd306cSNickeau } 656*04fd306cSNickeau 657*04fd306cSNickeau 658*04fd306cSNickeau public function setRequestedColor(ColorRgb $color): FetcherSvg 659*04fd306cSNickeau { 660*04fd306cSNickeau $this->color = $color; 661*04fd306cSNickeau return $this; 662*04fd306cSNickeau } 663*04fd306cSNickeau 664*04fd306cSNickeau /** 665*04fd306cSNickeau * @throws ExceptionNotFound 666*04fd306cSNickeau */ 667*04fd306cSNickeau public function getRequestedColor(): ColorRgb 668*04fd306cSNickeau { 669*04fd306cSNickeau if ($this->color === null) { 670*04fd306cSNickeau throw new ExceptionNotFound("No requested color"); 671*04fd306cSNickeau } 672*04fd306cSNickeau return $this->color; 673*04fd306cSNickeau } 674*04fd306cSNickeau 675*04fd306cSNickeau /** 676*04fd306cSNickeau * @param string $preserveAspectRatio - the aspect ratio of the svg 677*04fd306cSNickeau * @return $this 678*04fd306cSNickeau */ 679*04fd306cSNickeau public function setRequestedPreserveAspectRatio(string $preserveAspectRatio): FetcherSvg 680*04fd306cSNickeau { 681*04fd306cSNickeau $this->preserveAspectRatio = $preserveAspectRatio; 682*04fd306cSNickeau return $this; 683*04fd306cSNickeau } 684*04fd306cSNickeau 685*04fd306cSNickeau 686*04fd306cSNickeau /** 687*04fd306cSNickeau * @var string|null - a name identifier that is added in the SVG 688*04fd306cSNickeau */ 689*04fd306cSNickeau private ?string $requestedName = null; 690*04fd306cSNickeau 691*04fd306cSNickeau /** 692*04fd306cSNickeau * @var ?boolean do the svg should be optimized 693*04fd306cSNickeau */ 694*04fd306cSNickeau private ?bool $requestedOptimization = null; 695*04fd306cSNickeau 696*04fd306cSNickeau /** 697*04fd306cSNickeau * @var XmlDocument|null 698*04fd306cSNickeau */ 699*04fd306cSNickeau private ?XmlDocument $xmlDocument = null; 700*04fd306cSNickeau 701*04fd306cSNickeau 702*04fd306cSNickeau /** 703*04fd306cSNickeau * The name: 704*04fd306cSNickeau * * if this is a icon, this is the icon name of the {@link IconDownloader}. It's used to download the icon if not present. 705*04fd306cSNickeau * * is used to add a data attribute in the svg to be able to select it for test purpose 706*04fd306cSNickeau * 707*04fd306cSNickeau * @param string $name 708*04fd306cSNickeau * @return FetcherSvg 709*04fd306cSNickeau */ 710*04fd306cSNickeau public 711*04fd306cSNickeau function setRequestedName(string $name): FetcherSvg 712*04fd306cSNickeau { 713*04fd306cSNickeau $this->requestedName = $name; 714*04fd306cSNickeau return $this; 715*04fd306cSNickeau } 716*04fd306cSNickeau 717*04fd306cSNickeau 718*04fd306cSNickeau public 719*04fd306cSNickeau function __toString() 720*04fd306cSNickeau { 721*04fd306cSNickeau if (isset($this->name)) { 722*04fd306cSNickeau return $this->name; 723*04fd306cSNickeau } 724*04fd306cSNickeau if (isset($this->path)) { 725*04fd306cSNickeau try { 726*04fd306cSNickeau return $this->path->getLastNameWithoutExtension(); 727*04fd306cSNickeau } catch (ExceptionNotFound $e) { 728*04fd306cSNickeau LogUtility::internalError("root not possible, we should have a last name", self::CANONICAL); 729*04fd306cSNickeau return "Anonymous"; 730*04fd306cSNickeau } 731*04fd306cSNickeau } 732*04fd306cSNickeau return "Anonymous"; 733*04fd306cSNickeau 734*04fd306cSNickeau 735*04fd306cSNickeau } 736*04fd306cSNickeau 737*04fd306cSNickeau 738*04fd306cSNickeau /** 739*04fd306cSNickeau * @param string $viewBox 740*04fd306cSNickeau * @return string[] 741*04fd306cSNickeau */ 742*04fd306cSNickeau private function getViewBoxAttributes(string $viewBox): array 743*04fd306cSNickeau { 744*04fd306cSNickeau $attributes = explode(" ", $viewBox); 745*04fd306cSNickeau if (sizeof($attributes) === 1) { 746*04fd306cSNickeau /** 747*04fd306cSNickeau * We may find also comma. Example: 748*04fd306cSNickeau * viewBox="0,0,433.62,289.08" 749*04fd306cSNickeau */ 750*04fd306cSNickeau $attributes = explode(",", $viewBox); 751*04fd306cSNickeau } 752*04fd306cSNickeau return $attributes; 753*04fd306cSNickeau } 754*04fd306cSNickeau 755*04fd306cSNickeau 756*04fd306cSNickeau private function getXmlDocument(): XmlDocument 757*04fd306cSNickeau { 758*04fd306cSNickeau 759*04fd306cSNickeau $this->buildXmlDocumentIfNeeded(); 760*04fd306cSNickeau return $this->xmlDocument; 761*04fd306cSNickeau } 762*04fd306cSNickeau 763*04fd306cSNickeau /** 764*04fd306cSNickeau * Utility function 765*04fd306cSNickeau * @return \DOMDocument 766*04fd306cSNickeau */ 767*04fd306cSNickeau public function getXmlDom(): \DOMDocument 768*04fd306cSNickeau { 769*04fd306cSNickeau return $this->getXmlDocument()->getDomDocument(); 770*04fd306cSNickeau } 771*04fd306cSNickeau 772*04fd306cSNickeau /** 773*04fd306cSNickeau * @throws ExceptionNotFound 774*04fd306cSNickeau */ 775*04fd306cSNickeau public function getRequestedName(): string 776*04fd306cSNickeau { 777*04fd306cSNickeau if ($this->requestedName === null) { 778*04fd306cSNickeau throw new ExceptionNotFound("Name was not set"); 779*04fd306cSNickeau } 780*04fd306cSNickeau return $this->requestedName; 781*04fd306cSNickeau } 782*04fd306cSNickeau 783*04fd306cSNickeau public function setPreserveStyle(bool $bool): FetcherSvg 784*04fd306cSNickeau { 785*04fd306cSNickeau $this->preserveStyle = $bool; 786*04fd306cSNickeau return $this; 787*04fd306cSNickeau } 788*04fd306cSNickeau 789*04fd306cSNickeau public function getRequestedPreserveStyleOrDefault(): bool 790*04fd306cSNickeau { 791*04fd306cSNickeau try { 792*04fd306cSNickeau return $this->getRequestedPreserveStyle(); 793*04fd306cSNickeau } catch (ExceptionNotFound $e) { 794*04fd306cSNickeau return false; 795*04fd306cSNickeau } 796*04fd306cSNickeau } 797*04fd306cSNickeau 798*04fd306cSNickeau /** 799*04fd306cSNickeau * @throws ExceptionNotFound 800*04fd306cSNickeau */ 801*04fd306cSNickeau public function getRequestedType(): string 802*04fd306cSNickeau { 803*04fd306cSNickeau if ($this->requestedType === null) { 804*04fd306cSNickeau throw new ExceptionNotFound("The requested type was not specified"); 805*04fd306cSNickeau } 806*04fd306cSNickeau return $this->requestedType; 807*04fd306cSNickeau } 808*04fd306cSNickeau 809*04fd306cSNickeau /** 810*04fd306cSNickeau * @param string $markup - the svg as a string 811*04fd306cSNickeau * @param string $name - a name identifier (used in diff) 812*04fd306cSNickeau * @throws ExceptionBadSyntax 813*04fd306cSNickeau */ 814*04fd306cSNickeau private function setMarkup(string $markup, string $name): FetcherSvg 815*04fd306cSNickeau { 816*04fd306cSNickeau $this->name = $name; 817*04fd306cSNickeau $this->buildXmlDocumentIfNeeded($markup); 818*04fd306cSNickeau return $this; 819*04fd306cSNickeau } 820*04fd306cSNickeau 821*04fd306cSNickeau 822*04fd306cSNickeau public function setRequestedType(string $requestedType): FetcherSvg 823*04fd306cSNickeau { 824*04fd306cSNickeau $this->requestedType = $requestedType; 825*04fd306cSNickeau return $this; 826*04fd306cSNickeau } 827*04fd306cSNickeau 828*04fd306cSNickeau public function getRequestedHeight(): int 829*04fd306cSNickeau { 830*04fd306cSNickeau try { 831*04fd306cSNickeau return $this->getDefaultWidhtAndHeightForIconAndTileIfNotSet(); 832*04fd306cSNickeau } catch (ExceptionNotFound $e) { 833*04fd306cSNickeau return parent::getRequestedHeight(); 834*04fd306cSNickeau } 835*04fd306cSNickeau } 836*04fd306cSNickeau 837*04fd306cSNickeau public function getRequestedWidth(): int 838*04fd306cSNickeau { 839*04fd306cSNickeau try { 840*04fd306cSNickeau return $this->getDefaultWidhtAndHeightForIconAndTileIfNotSet(); 841*04fd306cSNickeau } catch (ExceptionNotFound $e) { 842*04fd306cSNickeau return parent::getRequestedWidth(); 843*04fd306cSNickeau } 844*04fd306cSNickeau } 845*04fd306cSNickeau 846*04fd306cSNickeau /** 847*04fd306cSNickeau * @throws ExceptionBadSyntax 848*04fd306cSNickeau * @throws ExceptionBadArgument 849*04fd306cSNickeau * @throws ExceptionBadState 850*04fd306cSNickeau * @throws ExceptionCompile 851*04fd306cSNickeau */ 852*04fd306cSNickeau public function process(): FetcherSvg 853*04fd306cSNickeau { 854*04fd306cSNickeau 855*04fd306cSNickeau if ($this->processed) { 856*04fd306cSNickeau LogUtility::internalError("The svg was already processed"); 857*04fd306cSNickeau return $this; 858*04fd306cSNickeau } 859*04fd306cSNickeau 860*04fd306cSNickeau $this->processed = true; 861*04fd306cSNickeau 862*04fd306cSNickeau // Handy variable 863*04fd306cSNickeau $documentElement = $this->getXmlDocument()->getElement(); 864*04fd306cSNickeau 865*04fd306cSNickeau 866*04fd306cSNickeau if ($this->getRequestedOptimizeOrDefault()) { 867*04fd306cSNickeau $this->optimize(); 868*04fd306cSNickeau } 869*04fd306cSNickeau 870*04fd306cSNickeau // Set the name (icon) attribute for test selection 871*04fd306cSNickeau try { 872*04fd306cSNickeau $name = $this->getRequestedNameOrDefault(); 873*04fd306cSNickeau $documentElement->setAttribute('data-name', $name); 874*04fd306cSNickeau } catch (ExceptionNotFound $e) { 875*04fd306cSNickeau // ok no name 876*04fd306cSNickeau } 877*04fd306cSNickeau 878*04fd306cSNickeau 879*04fd306cSNickeau // Width requested 880*04fd306cSNickeau try { 881*04fd306cSNickeau $requestedWidth = $this->getRequestedWidth(); 882*04fd306cSNickeau } catch (ExceptionNotFound $e) { 883*04fd306cSNickeau $requestedWidth = null; 884*04fd306cSNickeau } 885*04fd306cSNickeau 886*04fd306cSNickeau // Height requested 887*04fd306cSNickeau try { 888*04fd306cSNickeau $requestedHeight = $this->getRequestedHeight(); 889*04fd306cSNickeau } catch (ExceptionNotFound $e) { 890*04fd306cSNickeau $requestedHeight = null; 891*04fd306cSNickeau } 892*04fd306cSNickeau 893*04fd306cSNickeau 894*04fd306cSNickeau try { 895*04fd306cSNickeau $requestedType = $this->getRequestedType(); 896*04fd306cSNickeau } catch (ExceptionNotFound $e) { 897*04fd306cSNickeau $requestedType = null; 898*04fd306cSNickeau } 899*04fd306cSNickeau 900*04fd306cSNickeau /** 901*04fd306cSNickeau * Svg Structure 902*04fd306cSNickeau * 903*04fd306cSNickeau * All attributes that are applied for all usage (output independent) 904*04fd306cSNickeau * and that depends only on the structure of the icon 905*04fd306cSNickeau * 906*04fd306cSNickeau * Why ? Because {@link \syntax_plugin_combo_pageimage} 907*04fd306cSNickeau * can be an icon or an illustrative image 908*04fd306cSNickeau * 909*04fd306cSNickeau */ 910*04fd306cSNickeau $intrinsicWidth = $this->getIntrinsicWidth(); 911*04fd306cSNickeau $intrinsicHeight = $this->getIntrinsicHeight(); 912*04fd306cSNickeau 913*04fd306cSNickeau 914*04fd306cSNickeau $svgStructureType = $this->getInternalStructureType(); 915*04fd306cSNickeau 916*04fd306cSNickeau 917*04fd306cSNickeau /** 918*04fd306cSNickeau * Svg type 919*04fd306cSNickeau * The svg type is the svg usage 920*04fd306cSNickeau * How the svg should be shown (the usage) 921*04fd306cSNickeau * 922*04fd306cSNickeau * We need it to make the difference between an icon 923*04fd306cSNickeau * * in a paragraph (the width and height are the same) 924*04fd306cSNickeau * * as an illustration in a page image (the width and height may be not the same) 925*04fd306cSNickeau */ 926*04fd306cSNickeau if ($requestedType === null) { 927*04fd306cSNickeau switch ($svgStructureType) { 928*04fd306cSNickeau case FetcherSvg::ICON_TYPE: 929*04fd306cSNickeau $requestedType = FetcherSvg::ICON_TYPE; 930*04fd306cSNickeau break; 931*04fd306cSNickeau default: 932*04fd306cSNickeau $requestedType = FetcherSvg::ILLUSTRATION_TYPE; 933*04fd306cSNickeau break; 934*04fd306cSNickeau } 935*04fd306cSNickeau } 936*04fd306cSNickeau 937*04fd306cSNickeau /** 938*04fd306cSNickeau * A tag attributes to manage the add of style properties 939*04fd306cSNickeau * in the style attribute 940*04fd306cSNickeau */ 941*04fd306cSNickeau $extraAttributes = TagAttributes::createEmpty(self::TAG); 942*04fd306cSNickeau 943*04fd306cSNickeau /** 944*04fd306cSNickeau * Zoom occurs after the crop/dimenions setting if any 945*04fd306cSNickeau */ 946*04fd306cSNickeau try { 947*04fd306cSNickeau $zoomFactor = $this->getRequestedZoom(); 948*04fd306cSNickeau } catch (ExceptionNotFound $e) { 949*04fd306cSNickeau if ($svgStructureType === FetcherSvg::ICON_TYPE && $requestedType === FetcherSvg::ILLUSTRATION_TYPE) { 950*04fd306cSNickeau $zoomFactor = -4; 951*04fd306cSNickeau } else { 952*04fd306cSNickeau $zoomFactor = 1; // 0r 1 :) 953*04fd306cSNickeau } 954*04fd306cSNickeau } 955*04fd306cSNickeau 956*04fd306cSNickeau 957*04fd306cSNickeau /** 958*04fd306cSNickeau * Dimension processing (heigth, width, viewbox) 959*04fd306cSNickeau * 960*04fd306cSNickeau * ViewBox should exist 961*04fd306cSNickeau * 962*04fd306cSNickeau * Ratio / Width / Height Cropping happens via the viewbox 963*04fd306cSNickeau * 964*04fd306cSNickeau * Width and height used to set the viewBox of a svg 965*04fd306cSNickeau * to crop it (In a raster image, there is not this distinction) 966*04fd306cSNickeau * 967*04fd306cSNickeau * We set the viewbox everytime: 968*04fd306cSNickeau * If width and height are not the same, this is a crop 969*04fd306cSNickeau * If width and height are the same, this is not a crop 970*04fd306cSNickeau */ 971*04fd306cSNickeau $targetWidth = $this->getTargetWidth(); 972*04fd306cSNickeau $targetHeight = $this->getTargetHeight(); 973*04fd306cSNickeau if ($this->isCropRequested() || $zoomFactor !== 1) { 974*04fd306cSNickeau 975*04fd306cSNickeau /** 976*04fd306cSNickeau * ViewBox is the logical view 977*04fd306cSNickeau * 978*04fd306cSNickeau * with an icon case, we zoom out for illustation otherwise, this is ugly as the icon takes the whole place 979*04fd306cSNickeau * 980*04fd306cSNickeau * Zoom applies on the target/cropped dimension 981*04fd306cSNickeau * so that we can center all at once in the next step 982*04fd306cSNickeau */ 983*04fd306cSNickeau 984*04fd306cSNickeau /** 985*04fd306cSNickeau * The crop happens when we set the height and width on the svg. 986*04fd306cSNickeau * There is no need to manipulate the view box coordinate 987*04fd306cSNickeau */ 988*04fd306cSNickeau 989*04fd306cSNickeau /** 990*04fd306cSNickeau * Note: if the svg is an icon of width 24 with a viewbox of 0 0 24 24, 991*04fd306cSNickeau * if you double the viewbox to 0 0 48 48, you have applied of -2 992*04fd306cSNickeau * The icon is two times smaller smaller 993*04fd306cSNickeau */ 994*04fd306cSNickeau $viewBoxWidth = $this->getIntrinsicWidth(); 995*04fd306cSNickeau $viewBoxHeight = $this->getIntrinsicHeight(); 996*04fd306cSNickeau if ($zoomFactor < 0) { 997*04fd306cSNickeau $viewBoxWidth = -$zoomFactor * $viewBoxWidth; 998*04fd306cSNickeau $viewBoxHeight = -$zoomFactor * $viewBoxHeight; 999*04fd306cSNickeau } else { 1000*04fd306cSNickeau $viewBoxWidth = $viewBoxWidth / $zoomFactor; 1001*04fd306cSNickeau $viewBoxHeight = $viewBoxHeight / $zoomFactor; 1002*04fd306cSNickeau } 1003*04fd306cSNickeau 1004*04fd306cSNickeau 1005*04fd306cSNickeau /** 1006*04fd306cSNickeau * Center 1007*04fd306cSNickeau * 1008*04fd306cSNickeau * We center by moving the origin (ie x and y) 1009*04fd306cSNickeau */ 1010*04fd306cSNickeau $x = -($viewBoxWidth - $intrinsicWidth) / 2; 1011*04fd306cSNickeau $y = -($viewBoxHeight - $intrinsicHeight) / 2; 1012*04fd306cSNickeau $documentElement->setAttribute(FetcherSvg::VIEW_BOX, "$x $y $viewBoxWidth $viewBoxHeight"); 1013*04fd306cSNickeau 1014*04fd306cSNickeau } else { 1015*04fd306cSNickeau $viewBox = $documentElement->getAttribute(FetcherSvg::VIEW_BOX); 1016*04fd306cSNickeau if (empty($viewBox)) { 1017*04fd306cSNickeau // viewbox is mandatory 1018*04fd306cSNickeau $documentElement->setAttribute(FetcherSvg::VIEW_BOX, "0 0 {$this->getIntrinsicWidth()} {$this->getIntrinsicHeight()}"); 1019*04fd306cSNickeau } 1020*04fd306cSNickeau } 1021*04fd306cSNickeau /** 1022*04fd306cSNickeau * Dimension are mandatory 1023*04fd306cSNickeau * Why ? 1024*04fd306cSNickeau * - 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) 1025*04fd306cSNickeau * - to show the crop 1026*04fd306cSNickeau * - to have internal calculate dimension otherwise, it's tiny 1027*04fd306cSNickeau * - To have an internal width and not shrink on the css property `width: auto !important;` of a table 1028*04fd306cSNickeau * - To have an internal height and not shrink on the css property `height: auto !important;` of a table 1029*04fd306cSNickeau * - 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 1030*04fd306cSNickeau * - ... 1031*04fd306cSNickeau * */ 1032*04fd306cSNickeau $documentElement 1033*04fd306cSNickeau ->setAttribute(Dimension::WIDTH_KEY, $targetWidth) 1034*04fd306cSNickeau ->setAttribute(Dimension::HEIGHT_KEY, $targetHeight); 1035*04fd306cSNickeau 1036*04fd306cSNickeau /** 1037*04fd306cSNickeau * Css styling due to dimension 1038*04fd306cSNickeau */ 1039*04fd306cSNickeau switch ($requestedType) { 1040*04fd306cSNickeau case FetcherSvg::ICON_TYPE: 1041*04fd306cSNickeau case FetcherSvg::TILE_TYPE: 1042*04fd306cSNickeau 1043*04fd306cSNickeau if ($targetWidth !== $targetHeight) { 1044*04fd306cSNickeau /** 1045*04fd306cSNickeau * Check if the widht and height are the same 1046*04fd306cSNickeau * 1047*04fd306cSNickeau * Note: this is not the case for an illustration, 1048*04fd306cSNickeau * they may be different 1049*04fd306cSNickeau * They are not the width and height of the icon but 1050*04fd306cSNickeau * the width and height of the viewbox 1051*04fd306cSNickeau */ 1052*04fd306cSNickeau 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."); 1053*04fd306cSNickeau } 1054*04fd306cSNickeau 1055*04fd306cSNickeau break; 1056*04fd306cSNickeau default: 1057*04fd306cSNickeau /** 1058*04fd306cSNickeau * Illustration / Image 1059*04fd306cSNickeau */ 1060*04fd306cSNickeau /** 1061*04fd306cSNickeau * Responsive SVG 1062*04fd306cSNickeau */ 1063*04fd306cSNickeau try { 1064*04fd306cSNickeau $aspectRatio = $this->getRequestedPreserveAspectRatio(); 1065*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1066*04fd306cSNickeau /** 1067*04fd306cSNickeau * 1068*04fd306cSNickeau * Keep the same height 1069*04fd306cSNickeau * Image in the Middle and border deleted when resizing 1070*04fd306cSNickeau * https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio 1071*04fd306cSNickeau * Default is xMidYMid meet 1072*04fd306cSNickeau */ 1073*04fd306cSNickeau $aspectRatio = SiteConfig::getConfValue(FetcherSvg::CONF_PRESERVE_ASPECT_RATIO_DEFAULT, "xMidYMid slice"); 1074*04fd306cSNickeau } 1075*04fd306cSNickeau $documentElement->setAttribute("preserveAspectRatio", $aspectRatio); 1076*04fd306cSNickeau 1077*04fd306cSNickeau /** 1078*04fd306cSNickeau * Note on dimension width and height 1079*04fd306cSNickeau * Width and height element attribute are in reality css style properties. 1080*04fd306cSNickeau * ie the max-width style 1081*04fd306cSNickeau * They are treated in {@link PluginUtility::processStyle()} 1082*04fd306cSNickeau */ 1083*04fd306cSNickeau 1084*04fd306cSNickeau /** 1085*04fd306cSNickeau * Adapt to the container by default 1086*04fd306cSNickeau * Height `auto` and not `100%` otherwise you get a layout shift 1087*04fd306cSNickeau */ 1088*04fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("width", "100%"); 1089*04fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("height", "auto"); 1090*04fd306cSNickeau 1091*04fd306cSNickeau 1092*04fd306cSNickeau if ($requestedWidth !== null) { 1093*04fd306cSNickeau 1094*04fd306cSNickeau /** 1095*04fd306cSNickeau * If a dimension was set, it's seen by default as a max-width 1096*04fd306cSNickeau * If it should not such as in a card, this property is already set 1097*04fd306cSNickeau * and is not overwritten 1098*04fd306cSNickeau */ 1099*04fd306cSNickeau try { 1100*04fd306cSNickeau $widthInPixel = ConditionalLength::createFromString($requestedWidth)->toPixelNumber(); 1101*04fd306cSNickeau } catch (ExceptionCompile $e) { 1102*04fd306cSNickeau LogUtility::msg("The requested width $requestedWidth could not be converted to pixel. It returns the following error ({$e->getMessage()}). Processing was stopped"); 1103*04fd306cSNickeau return $this; 1104*04fd306cSNickeau } 1105*04fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("max-width", "{$widthInPixel}px"); 1106*04fd306cSNickeau 1107*04fd306cSNickeau } 1108*04fd306cSNickeau 1109*04fd306cSNickeau 1110*04fd306cSNickeau if ($requestedHeight !== null) { 1111*04fd306cSNickeau /** 1112*04fd306cSNickeau * If a dimension was set, it's seen by default as a max-width 1113*04fd306cSNickeau * If it should not such as in a card, this property is already set 1114*04fd306cSNickeau * and is not overwritten 1115*04fd306cSNickeau */ 1116*04fd306cSNickeau try { 1117*04fd306cSNickeau $heightInPixel = ConditionalLength::createFromString($requestedHeight)->toPixelNumber(); 1118*04fd306cSNickeau } catch (ExceptionCompile $e) { 1119*04fd306cSNickeau LogUtility::msg("The requested height $requestedHeight could not be converted to pixel. It returns the following error ({$e->getMessage()}). Processing was stopped"); 1120*04fd306cSNickeau return $this; 1121*04fd306cSNickeau } 1122*04fd306cSNickeau $extraAttributes->addStyleDeclarationIfNotSet("max-height", "{$heightInPixel}px"); 1123*04fd306cSNickeau 1124*04fd306cSNickeau 1125*04fd306cSNickeau } 1126*04fd306cSNickeau 1127*04fd306cSNickeau break; 1128*04fd306cSNickeau } 1129*04fd306cSNickeau 1130*04fd306cSNickeau 1131*04fd306cSNickeau switch ($svgStructureType) { 1132*04fd306cSNickeau case FetcherSvg::ICON_TYPE: 1133*04fd306cSNickeau case FetcherSvg::TILE_TYPE: 1134*04fd306cSNickeau /** 1135*04fd306cSNickeau * Determine if this is a: 1136*04fd306cSNickeau * * fill one color 1137*04fd306cSNickeau * * fill two colors 1138*04fd306cSNickeau * * or stroke svg icon 1139*04fd306cSNickeau * 1140*04fd306cSNickeau * The color can be set: 1141*04fd306cSNickeau * * on fill (surface) 1142*04fd306cSNickeau * * on stroke (line) 1143*04fd306cSNickeau * 1144*04fd306cSNickeau * If the stroke attribute is not present this is a fill icon 1145*04fd306cSNickeau */ 1146*04fd306cSNickeau $svgColorType = FetcherSvg::COLOR_TYPE_FILL_SOLID; 1147*04fd306cSNickeau if ($documentElement->hasAttribute(FetcherSvg::STROKE_ATTRIBUTE)) { 1148*04fd306cSNickeau $svgColorType = FetcherSvg::COLOR_TYPE_STROKE_OUTLINE; 1149*04fd306cSNickeau } 1150*04fd306cSNickeau /** 1151*04fd306cSNickeau * Double color icon ? 1152*04fd306cSNickeau */ 1153*04fd306cSNickeau $isDoubleColor = false; 1154*04fd306cSNickeau if ($svgColorType === FetcherSvg::COLOR_TYPE_FILL_SOLID) { 1155*04fd306cSNickeau $svgFillsElement = $this->getXmlDocument()->xpath("//*[@fill]"); 1156*04fd306cSNickeau $fillColors = []; 1157*04fd306cSNickeau for ($i = 0; $i < $svgFillsElement->length; $i++) { 1158*04fd306cSNickeau /** 1159*04fd306cSNickeau * @var DOMElement $nodeElement 1160*04fd306cSNickeau */ 1161*04fd306cSNickeau $nodeElement = $svgFillsElement[$i]; 1162*04fd306cSNickeau $value = $nodeElement->getAttribute("fill"); 1163*04fd306cSNickeau if ($value !== "none") { 1164*04fd306cSNickeau /** 1165*04fd306cSNickeau * Icon may have none alongside colors 1166*04fd306cSNickeau * Example: 1167*04fd306cSNickeau */ 1168*04fd306cSNickeau $fillColors[$value] = $value; 1169*04fd306cSNickeau } 1170*04fd306cSNickeau } 1171*04fd306cSNickeau if (sizeof($fillColors) > 1) { 1172*04fd306cSNickeau $isDoubleColor = true; 1173*04fd306cSNickeau } 1174*04fd306cSNickeau } 1175*04fd306cSNickeau 1176*04fd306cSNickeau /** 1177*04fd306cSNickeau * CurrentColor 1178*04fd306cSNickeau * 1179*04fd306cSNickeau * By default, the icon should have this property when downloaded 1180*04fd306cSNickeau * but if this not the case (such as for Material design), we set them 1181*04fd306cSNickeau * 1182*04fd306cSNickeau * Feather set it on the stroke 1183*04fd306cSNickeau * Example: view-source:https://raw.githubusercontent.com/feathericons/feather/master/icons/airplay.svg 1184*04fd306cSNickeau * <svg 1185*04fd306cSNickeau * fill="none" 1186*04fd306cSNickeau * stroke="currentColor"> 1187*04fd306cSNickeau */ 1188*04fd306cSNickeau if (!$isDoubleColor && !$documentElement->hasAttribute("fill")) { 1189*04fd306cSNickeau 1190*04fd306cSNickeau /** 1191*04fd306cSNickeau * Note: if fill was not set, the default color would be black 1192*04fd306cSNickeau */ 1193*04fd306cSNickeau $documentElement->setAttribute("fill", FetcherSvg::CURRENT_COLOR); 1194*04fd306cSNickeau 1195*04fd306cSNickeau } 1196*04fd306cSNickeau 1197*04fd306cSNickeau 1198*04fd306cSNickeau /** 1199*04fd306cSNickeau * Eva/Carbon Source Icon are not optimized at the source 1200*04fd306cSNickeau * Example: 1201*04fd306cSNickeau * * eva:facebook-fill 1202*04fd306cSNickeau * * carbon:logo-tumblr (https://github.com/carbon-design-system/carbon/issues/5568) 1203*04fd306cSNickeau * 1204*04fd306cSNickeau * We delete the rectangle 1205*04fd306cSNickeau * Style should have already been deleted by the optimization 1206*04fd306cSNickeau * 1207*04fd306cSNickeau * This optimization should happen if the color is set 1208*04fd306cSNickeau * or not because we set the color value to `currentColor` 1209*04fd306cSNickeau * 1210*04fd306cSNickeau * If the rectangle stay, we just see a black rectangle 1211*04fd306cSNickeau */ 1212*04fd306cSNickeau try { 1213*04fd306cSNickeau $path = $this->getSourcePath(); 1214*04fd306cSNickeau $pathString = $path->toAbsolutePath()->toAbsoluteId(); 1215*04fd306cSNickeau if ( 1216*04fd306cSNickeau preg_match("/carbon|eva/i", $pathString) === 1 1217*04fd306cSNickeau ) { 1218*04fd306cSNickeau XmlSystems::deleteAllElementsByName("rect", $this->getXmlDocument()); 1219*04fd306cSNickeau } 1220*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1221*04fd306cSNickeau // ok 1222*04fd306cSNickeau } 1223*04fd306cSNickeau 1224*04fd306cSNickeau 1225*04fd306cSNickeau $color = null; 1226*04fd306cSNickeau try { 1227*04fd306cSNickeau $color = $this->getRequestedColor(); 1228*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1229*04fd306cSNickeau if ($requestedType === FetcherSvg::ILLUSTRATION_TYPE) { 1230*04fd306cSNickeau $primaryColor = Site::getPrimaryColorValue(); 1231*04fd306cSNickeau if ($primaryColor !== null) { 1232*04fd306cSNickeau $color = ColorRgb::createFromString($primaryColor); 1233*04fd306cSNickeau } 1234*04fd306cSNickeau } 1235*04fd306cSNickeau } 1236*04fd306cSNickeau 1237*04fd306cSNickeau 1238*04fd306cSNickeau /** 1239*04fd306cSNickeau * Color 1240*04fd306cSNickeau * Color applies only if this is an icon. 1241*04fd306cSNickeau * 1242*04fd306cSNickeau */ 1243*04fd306cSNickeau if ($color !== null) { 1244*04fd306cSNickeau /** 1245*04fd306cSNickeau * 1246*04fd306cSNickeau * We say that this is used only for an icon (<72 px) 1247*04fd306cSNickeau * 1248*04fd306cSNickeau * Not that an icon svg file can also be used as {@link \syntax_plugin_combo_pageimage} 1249*04fd306cSNickeau * 1250*04fd306cSNickeau * We don't set it as a styling attribute 1251*04fd306cSNickeau * because it's not taken into account if the 1252*04fd306cSNickeau * svg is used as a background image 1253*04fd306cSNickeau * fill or stroke should have at minimum "currentColor" 1254*04fd306cSNickeau */ 1255*04fd306cSNickeau $colorValue = $color->toCssValue(); 1256*04fd306cSNickeau 1257*04fd306cSNickeau 1258*04fd306cSNickeau switch ($svgColorType) { 1259*04fd306cSNickeau case FetcherSvg::COLOR_TYPE_FILL_SOLID: 1260*04fd306cSNickeau 1261*04fd306cSNickeau if (!$isDoubleColor) { 1262*04fd306cSNickeau 1263*04fd306cSNickeau $documentElement->setAttribute("fill", $colorValue); 1264*04fd306cSNickeau 1265*04fd306cSNickeau if ($colorValue !== FetcherSvg::CURRENT_COLOR) { 1266*04fd306cSNickeau /** 1267*04fd306cSNickeau * Update the fill property on sub-path 1268*04fd306cSNickeau * If the fill is set on sub-path, it will not work 1269*04fd306cSNickeau * 1270*04fd306cSNickeau * fill may be set on group or whatever 1271*04fd306cSNickeau */ 1272*04fd306cSNickeau $svgPaths = $this->getXmlDocument()->xpath("//*[local-name()='path' or local-name()='g']"); 1273*04fd306cSNickeau for ($i = 0; $i < $svgPaths->length; $i++) { 1274*04fd306cSNickeau /** 1275*04fd306cSNickeau * @var DOMElement $nodeElement 1276*04fd306cSNickeau */ 1277*04fd306cSNickeau $nodeElement = $svgPaths[$i]; 1278*04fd306cSNickeau $value = $nodeElement->getAttribute("fill"); 1279*04fd306cSNickeau if ($value !== "none") { 1280*04fd306cSNickeau if ($nodeElement->parentNode->tagName !== "svg") { 1281*04fd306cSNickeau $nodeElement->setAttribute("fill", FetcherSvg::CURRENT_COLOR); 1282*04fd306cSNickeau } else { 1283*04fd306cSNickeau $this->getXmlDocument()->removeAttributeValue("fill", $nodeElement); 1284*04fd306cSNickeau } 1285*04fd306cSNickeau } 1286*04fd306cSNickeau } 1287*04fd306cSNickeau 1288*04fd306cSNickeau } 1289*04fd306cSNickeau } else { 1290*04fd306cSNickeau // double color 1291*04fd306cSNickeau $firsFillElement = $this->getXmlDocument()->xpath("//*[@fill][1]")->item(0); 1292*04fd306cSNickeau if ($firsFillElement instanceof DOMElement) { 1293*04fd306cSNickeau $firsFillElement->setAttribute("fill", $colorValue); 1294*04fd306cSNickeau } 1295*04fd306cSNickeau } 1296*04fd306cSNickeau break; 1297*04fd306cSNickeau 1298*04fd306cSNickeau case FetcherSvg::COLOR_TYPE_STROKE_OUTLINE: 1299*04fd306cSNickeau $documentElement->setAttribute("fill", "none"); 1300*04fd306cSNickeau $documentElement->setAttribute(FetcherSvg::STROKE_ATTRIBUTE, $colorValue); 1301*04fd306cSNickeau 1302*04fd306cSNickeau if ($colorValue !== FetcherSvg::CURRENT_COLOR) { 1303*04fd306cSNickeau /** 1304*04fd306cSNickeau * Delete the stroke property on sub-path 1305*04fd306cSNickeau */ 1306*04fd306cSNickeau // if the fill is set on sub-path, it will not work 1307*04fd306cSNickeau $svgPaths = $this->getXmlDocument()->xpath("//*[local-name()='path']"); 1308*04fd306cSNickeau for ($i = 0; $i < $svgPaths->length; $i++) { 1309*04fd306cSNickeau /** 1310*04fd306cSNickeau * @var DOMElement $nodeElement 1311*04fd306cSNickeau */ 1312*04fd306cSNickeau $nodeElement = $svgPaths[$i]; 1313*04fd306cSNickeau $value = $nodeElement->getAttribute(FetcherSvg::STROKE_ATTRIBUTE); 1314*04fd306cSNickeau if ($value !== "none") { 1315*04fd306cSNickeau $this->getXmlDocument()->removeAttributeValue(FetcherSvg::STROKE_ATTRIBUTE, $nodeElement); 1316*04fd306cSNickeau } else { 1317*04fd306cSNickeau $this->getXmlDocument()->removeNode($nodeElement); 1318*04fd306cSNickeau } 1319*04fd306cSNickeau } 1320*04fd306cSNickeau 1321*04fd306cSNickeau } 1322*04fd306cSNickeau break; 1323*04fd306cSNickeau } 1324*04fd306cSNickeau 1325*04fd306cSNickeau } 1326*04fd306cSNickeau break; 1327*04fd306cSNickeau 1328*04fd306cSNickeau } 1329*04fd306cSNickeau 1330*04fd306cSNickeau 1331*04fd306cSNickeau /** 1332*04fd306cSNickeau * Set the attributes to the root element 1333*04fd306cSNickeau * Svg attribute are case sensitive 1334*04fd306cSNickeau * Styling 1335*04fd306cSNickeau */ 1336*04fd306cSNickeau $extraAttributeAsArray = $extraAttributes->toHtmlArray(); 1337*04fd306cSNickeau foreach ($extraAttributeAsArray as $name => $value) { 1338*04fd306cSNickeau $documentElement->setAttribute($name, $value); 1339*04fd306cSNickeau } 1340*04fd306cSNickeau 1341*04fd306cSNickeau /** 1342*04fd306cSNickeau * Class 1343*04fd306cSNickeau */ 1344*04fd306cSNickeau try { 1345*04fd306cSNickeau $class = $this->getRequestedClass(); 1346*04fd306cSNickeau $documentElement->addClass($class); 1347*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1348*04fd306cSNickeau // no class 1349*04fd306cSNickeau } 1350*04fd306cSNickeau // add class with svg type 1351*04fd306cSNickeau $documentElement 1352*04fd306cSNickeau ->addClass(StyleAttribute::addComboStrapSuffix(self::TAG)) 1353*04fd306cSNickeau ->addClass(StyleAttribute::addComboStrapSuffix(self::TAG . "-" . $requestedType)); 1354*04fd306cSNickeau // Add a class on each path for easy styling 1355*04fd306cSNickeau try { 1356*04fd306cSNickeau $name = $this->getRequestedNameOrDefault(); 1357*04fd306cSNickeau $svgPaths = $documentElement->querySelectorAll('path'); 1358*04fd306cSNickeau for ($i = 0; 1359*04fd306cSNickeau $i < count($svgPaths); 1360*04fd306cSNickeau $i++) { 1361*04fd306cSNickeau $element = $svgPaths[$i]; 1362*04fd306cSNickeau $stylingClass = $name . "-" . $i; 1363*04fd306cSNickeau $element->addClass($stylingClass); 1364*04fd306cSNickeau } 1365*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1366*04fd306cSNickeau // no name 1367*04fd306cSNickeau } 1368*04fd306cSNickeau 1369*04fd306cSNickeau return $this; 1370*04fd306cSNickeau 1371*04fd306cSNickeau } 1372*04fd306cSNickeau 1373*04fd306cSNickeau 1374*04fd306cSNickeau public function getFetcherName(): string 1375*04fd306cSNickeau { 1376*04fd306cSNickeau return self::CANONICAL; 1377*04fd306cSNickeau } 1378*04fd306cSNickeau 1379*04fd306cSNickeau /** 1380*04fd306cSNickeau * @throws ExceptionBadArgument 1381*04fd306cSNickeau * @throws ExceptionBadSyntax 1382*04fd306cSNickeau * @throws ExceptionCompile 1383*04fd306cSNickeau */ 1384*04fd306cSNickeau public function buildFromTagAttributes(TagAttributes $tagAttributes): FetcherImage 1385*04fd306cSNickeau { 1386*04fd306cSNickeau 1387*04fd306cSNickeau foreach (array_keys($tagAttributes->getComponentAttributes()) as $svgAttribute) { 1388*04fd306cSNickeau $svgAttribute = strtolower($svgAttribute); 1389*04fd306cSNickeau switch ($svgAttribute) { 1390*04fd306cSNickeau case Dimension::WIDTH_KEY: 1391*04fd306cSNickeau case Dimension::HEIGHT_KEY: 1392*04fd306cSNickeau /** 1393*04fd306cSNickeau * Length may be defined with CSS unit 1394*04fd306cSNickeau * https://www.w3.org/TR/SVG2/coords.html#Units 1395*04fd306cSNickeau */ 1396*04fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 1397*04fd306cSNickeau try { 1398*04fd306cSNickeau $lengthInt = ConditionalLength::createFromString($value)->toPixelNumber(); 1399*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 1400*04fd306cSNickeau LogUtility::error("The $svgAttribute value ($value) of the svg ($this) is not an integer", self::CANONICAL); 1401*04fd306cSNickeau continue 2; 1402*04fd306cSNickeau } 1403*04fd306cSNickeau if ($svgAttribute === Dimension::WIDTH_KEY) { 1404*04fd306cSNickeau $this->setRequestedWidth($lengthInt); 1405*04fd306cSNickeau } else { 1406*04fd306cSNickeau $this->setRequestedHeight($lengthInt); 1407*04fd306cSNickeau } 1408*04fd306cSNickeau continue 2; 1409*04fd306cSNickeau case Dimension::ZOOM_ATTRIBUTE; 1410*04fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 1411*04fd306cSNickeau try { 1412*04fd306cSNickeau $lengthFloat = DataType::toFloat($value); 1413*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 1414*04fd306cSNickeau LogUtility::error("The $svgAttribute value ($value) of the svg ($this) is not a float", self::CANONICAL); 1415*04fd306cSNickeau continue 2; 1416*04fd306cSNickeau } 1417*04fd306cSNickeau $this->setRequestedZoom($lengthFloat); 1418*04fd306cSNickeau continue 2; 1419*04fd306cSNickeau case ColorRgb::COLOR: 1420*04fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 1421*04fd306cSNickeau try { 1422*04fd306cSNickeau $color = ColorRgb::createFromString($value); 1423*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 1424*04fd306cSNickeau LogUtility::error("The $svgAttribute value ($value) of the svg ($this) is not an valid color", self::CANONICAL); 1425*04fd306cSNickeau continue 2; 1426*04fd306cSNickeau } 1427*04fd306cSNickeau $this->setRequestedColor($color); 1428*04fd306cSNickeau continue 2; 1429*04fd306cSNickeau case TagAttributes::TYPE_KEY: 1430*04fd306cSNickeau $value = $tagAttributes->getValue($svgAttribute); 1431*04fd306cSNickeau $this->setRequestedType($value); 1432*04fd306cSNickeau continue 2; 1433*04fd306cSNickeau case self::REQUESTED_PRESERVE_ATTRIBUTE: 1434*04fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 1435*04fd306cSNickeau if ($value === "style") { 1436*04fd306cSNickeau $preserve = true; 1437*04fd306cSNickeau } else { 1438*04fd306cSNickeau $preserve = false; 1439*04fd306cSNickeau } 1440*04fd306cSNickeau $this->setPreserveStyle($preserve); 1441*04fd306cSNickeau continue 2; 1442*04fd306cSNickeau case self::NAME_ATTRIBUTE: 1443*04fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 1444*04fd306cSNickeau $this->setRequestedName($value); 1445*04fd306cSNickeau continue 2; 1446*04fd306cSNickeau case TagAttributes::CLASS_KEY: 1447*04fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 1448*04fd306cSNickeau $this->setRequestedClass($value); 1449*04fd306cSNickeau continue 2; 1450*04fd306cSNickeau case strtolower(self::REQUESTED_PRESERVE_ASPECT_RATIO_KEY): 1451*04fd306cSNickeau $value = $tagAttributes->getValueAndRemove($svgAttribute); 1452*04fd306cSNickeau $this->setRequestedPreserveAspectRatio($value); 1453*04fd306cSNickeau continue 2; 1454*04fd306cSNickeau } 1455*04fd306cSNickeau 1456*04fd306cSNickeau } 1457*04fd306cSNickeau 1458*04fd306cSNickeau /** 1459*04fd306cSNickeau * Icon case 1460*04fd306cSNickeau */ 1461*04fd306cSNickeau try { 1462*04fd306cSNickeau $iconDownload = 1463*04fd306cSNickeau !$tagAttributes->hasAttribute(FetcherTraitWikiPath::$MEDIA_QUERY_PARAMETER) && 1464*04fd306cSNickeau $this->getRequestedType() === self::ICON_TYPE 1465*04fd306cSNickeau && $this->getRequestedName() !== null; 1466*04fd306cSNickeau if ($iconDownload) { 1467*04fd306cSNickeau try { 1468*04fd306cSNickeau $dokuPath = $this->downloadAndGetIconPath(); 1469*04fd306cSNickeau } catch (ExceptionCompile $e) { 1470*04fd306cSNickeau 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); 1471*04fd306cSNickeau } 1472*04fd306cSNickeau $this->setSourcePath($dokuPath); 1473*04fd306cSNickeau 1474*04fd306cSNickeau } 1475*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1476*04fd306cSNickeau // no requested type or name 1477*04fd306cSNickeau } 1478*04fd306cSNickeau 1479*04fd306cSNickeau /** 1480*04fd306cSNickeau * Raw Trait 1481*04fd306cSNickeau */ 1482*04fd306cSNickeau $this->buildOriginalPathFromTagAttributes($tagAttributes); 1483*04fd306cSNickeau parent::buildFromTagAttributes($tagAttributes); 1484*04fd306cSNickeau return $this; 1485*04fd306cSNickeau } 1486*04fd306cSNickeau 1487*04fd306cSNickeau /** 1488*04fd306cSNickeau * @throws ExceptionBadArgument 1489*04fd306cSNickeau * @throws ExceptionCompile 1490*04fd306cSNickeau * @throws ExceptionBadSyntax 1491*04fd306cSNickeau * @throws ExceptionNotFound 1492*04fd306cSNickeau */ 1493*04fd306cSNickeau private function downloadAndGetIconPath(): WikiPath 1494*04fd306cSNickeau { 1495*04fd306cSNickeau /** 1496*04fd306cSNickeau * It may be a Svg icon that we needs to download 1497*04fd306cSNickeau */ 1498*04fd306cSNickeau try { 1499*04fd306cSNickeau $requestedType = $this->getRequestedType(); 1500*04fd306cSNickeau $requestedName = $this->getRequestedName(); 1501*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1502*04fd306cSNickeau throw new ExceptionNotFound("No path was defined and no icon name was defined"); 1503*04fd306cSNickeau } 1504*04fd306cSNickeau if ($requestedType !== self::ICON_TYPE) { 1505*04fd306cSNickeau throw new ExceptionNotFound("No original path was set and no icon was defined"); 1506*04fd306cSNickeau } 1507*04fd306cSNickeau 1508*04fd306cSNickeau try { 1509*04fd306cSNickeau $iconDownloader = IconDownloader::createFromName($requestedName); 1510*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 1511*04fd306cSNickeau throw new ExceptionNotFound("The name ($requestedName) is not a valid icon name. Error: ({$e->getMessage()}.", self::CANONICAL, 1, $e); 1512*04fd306cSNickeau } 1513*04fd306cSNickeau $originalPath = $iconDownloader->getPath(); 1514*04fd306cSNickeau if (FileSystems::exists($originalPath)) { 1515*04fd306cSNickeau return $originalPath; 1516*04fd306cSNickeau } 1517*04fd306cSNickeau try { 1518*04fd306cSNickeau $iconDownloader->download(); 1519*04fd306cSNickeau } catch (ExceptionCompile $e) { 1520*04fd306cSNickeau throw new ExceptionCompile("The icon ($requestedName) could not be downloaded. Error: ({$e->getMessage()}.", self::CANONICAL); 1521*04fd306cSNickeau } 1522*04fd306cSNickeau $this->setSourcePath($originalPath); 1523*04fd306cSNickeau return $originalPath; 1524*04fd306cSNickeau } 1525*04fd306cSNickeau 1526*04fd306cSNickeau /** 1527*04fd306cSNickeau * This is used to add a name and class to the svg to make selection more easy 1528*04fd306cSNickeau * @throws ExceptionBadState 1529*04fd306cSNickeau * @throws ExceptionNotFound 1530*04fd306cSNickeau */ 1531*04fd306cSNickeau private function getRequestedNameOrDefault(): string 1532*04fd306cSNickeau { 1533*04fd306cSNickeau try { 1534*04fd306cSNickeau return $this->getRequestedName(); 1535*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1536*04fd306cSNickeau return $this->getSourcePath()->getLastNameWithoutExtension(); 1537*04fd306cSNickeau } 1538*04fd306cSNickeau } 1539*04fd306cSNickeau 1540*04fd306cSNickeau /** 1541*04fd306cSNickeau * @return bool - true if no width or height was requested 1542*04fd306cSNickeau */ 1543*04fd306cSNickeau private function norWidthNorHeightWasRequested(): bool 1544*04fd306cSNickeau { 1545*04fd306cSNickeau 1546*04fd306cSNickeau if ($this->requestedWidth !== null) { 1547*04fd306cSNickeau return false; 1548*04fd306cSNickeau } 1549*04fd306cSNickeau if ($this->requestedHeight !== null) { 1550*04fd306cSNickeau return false; 1551*04fd306cSNickeau } 1552*04fd306cSNickeau return true; 1553*04fd306cSNickeau 1554*04fd306cSNickeau } 1555*04fd306cSNickeau 1556*04fd306cSNickeau /** 1557*04fd306cSNickeau * @throws ExceptionNotFound 1558*04fd306cSNickeau */ 1559*04fd306cSNickeau private function getRequestedZoom(): float 1560*04fd306cSNickeau { 1561*04fd306cSNickeau $zoom = $this->zoomFactor; 1562*04fd306cSNickeau if ($zoom === null) { 1563*04fd306cSNickeau throw new ExceptionNotFound("No zoom requested"); 1564*04fd306cSNickeau } 1565*04fd306cSNickeau return $zoom; 1566*04fd306cSNickeau } 1567*04fd306cSNickeau 1568*04fd306cSNickeau public function setRequestedZoom(float $zoomFactor): FetcherSvg 1569*04fd306cSNickeau { 1570*04fd306cSNickeau $this->zoomFactor = $zoomFactor; 1571*04fd306cSNickeau return $this; 1572*04fd306cSNickeau } 1573*04fd306cSNickeau 1574*04fd306cSNickeau public function setRequestedClass(string $value): FetcherSvg 1575*04fd306cSNickeau { 1576*04fd306cSNickeau $this->requestedClass = $value; 1577*04fd306cSNickeau return $this; 1578*04fd306cSNickeau 1579*04fd306cSNickeau } 1580*04fd306cSNickeau 1581*04fd306cSNickeau /** 1582*04fd306cSNickeau * @throws ExceptionNotFound 1583*04fd306cSNickeau */ 1584*04fd306cSNickeau private function getRequestedClass(): string 1585*04fd306cSNickeau { 1586*04fd306cSNickeau if ($this->requestedClass === null) { 1587*04fd306cSNickeau throw new ExceptionNotFound("No class was set"); 1588*04fd306cSNickeau } 1589*04fd306cSNickeau return $this->requestedClass; 1590*04fd306cSNickeau } 1591*04fd306cSNickeau 1592*04fd306cSNickeau /** 1593*04fd306cSNickeau * Analyse and set the mandatory intrinsic dimensions 1594*04fd306cSNickeau * @throws ExceptionBadSyntax 1595*04fd306cSNickeau */ 1596*04fd306cSNickeau private function setIntrinsicDimensions() 1597*04fd306cSNickeau { 1598*04fd306cSNickeau $this->setIntrinsicHeight() 1599*04fd306cSNickeau ->setIntrinsicWidth(); 1600*04fd306cSNickeau } 1601*04fd306cSNickeau 1602*04fd306cSNickeau /** 1603*04fd306cSNickeau * @throws ExceptionBadSyntax 1604*04fd306cSNickeau */ 1605*04fd306cSNickeau private function setIntrinsicHeight(): FetcherSvg 1606*04fd306cSNickeau { 1607*04fd306cSNickeau $viewBox = $this->getXmlDocument()->getDomDocument()->documentElement->getAttribute(FetcherSvg::VIEW_BOX); 1608*04fd306cSNickeau if ($viewBox !== "") { 1609*04fd306cSNickeau $attributes = $this->getViewBoxAttributes($viewBox); 1610*04fd306cSNickeau $viewBoxHeight = $attributes[3]; 1611*04fd306cSNickeau try { 1612*04fd306cSNickeau /** 1613*04fd306cSNickeau * Ceil because we want to see a border if there is one 1614*04fd306cSNickeau */ 1615*04fd306cSNickeau $this->intrinsicHeight = DataType::toIntegerCeil($viewBoxHeight); 1616*04fd306cSNickeau return $this; 1617*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 1618*04fd306cSNickeau throw new ExceptionBadSyntax("The media height ($viewBoxHeight) of the svg image ($this) is not a valid integer value"); 1619*04fd306cSNickeau } 1620*04fd306cSNickeau } 1621*04fd306cSNickeau /** 1622*04fd306cSNickeau * Case with some icon such as 1623*04fd306cSNickeau * https://raw.githubusercontent.com/fefanto/fontaudio/master/svgs/fad-random-1dice.svg 1624*04fd306cSNickeau */ 1625*04fd306cSNickeau $height = $this->getXmlDocument()->getDomDocument()->documentElement->getAttribute("height"); 1626*04fd306cSNickeau if ($height === "") { 1627*04fd306cSNickeau throw new ExceptionBadSyntax("The svg ($this) does not have a viewBox or height attribute, the intrinsic height cannot be determined"); 1628*04fd306cSNickeau } 1629*04fd306cSNickeau try { 1630*04fd306cSNickeau $this->intrinsicHeight = DataType::toInteger($height); 1631*04fd306cSNickeau } catch (ExceptionBadArgument $e) { 1632*04fd306cSNickeau throw new ExceptionBadSyntax("The media width ($height) of the svg image ($this) is not a valid integer value"); 1633*04fd306cSNickeau } 1634*04fd306cSNickeau return $this; 1635*04fd306cSNickeau } 1636*04fd306cSNickeau 1637*04fd306cSNickeau /** 1638*04fd306cSNickeau * @throws ExceptionBadSyntax 1639*04fd306cSNickeau */ 1640*04fd306cSNickeau private function setIntrinsicWidth(): FetcherSvg 1641*04fd306cSNickeau { 1642*04fd306cSNickeau $viewBox = $this->getXmlDom()->documentElement->getAttribute(FetcherSvg::VIEW_BOX); 1643*04fd306cSNickeau if ($viewBox !== "") { 1644*04fd306cSNickeau $attributes = $this->getViewBoxAttributes($viewBox); 1645*04fd306cSNickeau $viewBoxWidth = $attributes[2]; 1646*04fd306cSNickeau try { 1647*04fd306cSNickeau /** 1648*04fd306cSNickeau * Ceil because we want to see a border if there is one 1649*04fd306cSNickeau */ 1650*04fd306cSNickeau $this->intrinsicWidth = DataType::toIntegerCeil($viewBoxWidth); 1651*04fd306cSNickeau return $this; 1652*04fd306cSNickeau } catch (ExceptionCompile $e) { 1653*04fd306cSNickeau throw new ExceptionBadSyntax("The media with ($viewBoxWidth) of the svg image ($this) is not a valid integer value"); 1654*04fd306cSNickeau } 1655*04fd306cSNickeau } 1656*04fd306cSNickeau 1657*04fd306cSNickeau /** 1658*04fd306cSNickeau * Case with some icon such as 1659*04fd306cSNickeau * https://raw.githubusercontent.com/fefanto/fontaudio/master/svgs/fad-random-1dice.svg 1660*04fd306cSNickeau */ 1661*04fd306cSNickeau $width = $this->getXmlDom()->documentElement->getAttribute("width"); 1662*04fd306cSNickeau if ($width === "") { 1663*04fd306cSNickeau throw new ExceptionBadSyntax("The svg ($this) does not have a viewBox or width attribute, the intrinsic width cannot be determined"); 1664*04fd306cSNickeau } 1665*04fd306cSNickeau try { 1666*04fd306cSNickeau $this->intrinsicWidth = DataType::toInteger($width); 1667*04fd306cSNickeau return $this; 1668*04fd306cSNickeau } catch (ExceptionCompile $e) { 1669*04fd306cSNickeau throw new ExceptionBadSyntax("The media width ($width) of the svg image ($this) is not a valid integer value"); 1670*04fd306cSNickeau } 1671*04fd306cSNickeau } 1672*04fd306cSNickeau 1673*04fd306cSNickeau /** 1674*04fd306cSNickeau * Build is done late because we want to be able to create a fetch url even if the file is not a correct svg 1675*04fd306cSNickeau * 1676*04fd306cSNickeau * The downside is that there is an exception that may be triggered all over the place 1677*04fd306cSNickeau * 1678*04fd306cSNickeau * 1679*04fd306cSNickeau * @throws ExceptionBadSyntax 1680*04fd306cSNickeau */ 1681*04fd306cSNickeau private function buildXmlDocumentIfNeeded(string $markup = null): FetcherSvg 1682*04fd306cSNickeau { 1683*04fd306cSNickeau /** 1684*04fd306cSNickeau * The svg document may be build 1685*04fd306cSNickeau * via markup (See {@link self::setMarkup()} 1686*04fd306cSNickeau */ 1687*04fd306cSNickeau if ($this->xmlDocument !== null) { 1688*04fd306cSNickeau return $this; 1689*04fd306cSNickeau } 1690*04fd306cSNickeau 1691*04fd306cSNickeau /** 1692*04fd306cSNickeau * Markup string passed directly or 1693*04fd306cSNickeau * via the source path below 1694*04fd306cSNickeau */ 1695*04fd306cSNickeau if ($markup !== null) { 1696*04fd306cSNickeau $this->xmlDocument = XmlDocument::createXmlDocFromMarkup($markup); 1697*04fd306cSNickeau $localName = $this->xmlDocument->getElement()->getLocalName(); 1698*04fd306cSNickeau if ($localName !== "svg") { 1699*04fd306cSNickeau throw new ExceptionBadSyntax("This is not a svg but a $localName element."); 1700*04fd306cSNickeau } 1701*04fd306cSNickeau $this->setIntrinsicDimensions(); 1702*04fd306cSNickeau return $this; 1703*04fd306cSNickeau } 1704*04fd306cSNickeau 1705*04fd306cSNickeau /** 1706*04fd306cSNickeau * A svg path 1707*04fd306cSNickeau * 1708*04fd306cSNickeau * Because we test bad svg, we want to be able to build an url. 1709*04fd306cSNickeau * We don't want therefore to throw when the svg file is not valid 1710*04fd306cSNickeau * We therefore check the validity at runtime 1711*04fd306cSNickeau */ 1712*04fd306cSNickeau $path = $this->getSourcePath(); 1713*04fd306cSNickeau try { 1714*04fd306cSNickeau $markup = FileSystems::getContent($path); 1715*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1716*04fd306cSNickeau throw new ExceptionRuntime("The svg file ($path) was not found", self::CANONICAL); 1717*04fd306cSNickeau } 1718*04fd306cSNickeau try { 1719*04fd306cSNickeau $this->buildXmlDocumentIfNeeded($markup); 1720*04fd306cSNickeau } catch (ExceptionBadSyntax $e) { 1721*04fd306cSNickeau throw new ExceptionRuntime("The svg file ($path) is not a valid svg. Error: {$e->getMessage()}"); 1722*04fd306cSNickeau } 1723*04fd306cSNickeau 1724*04fd306cSNickeau // dimension 1725*04fd306cSNickeau return $this; 1726*04fd306cSNickeau 1727*04fd306cSNickeau } 1728*04fd306cSNickeau 1729*04fd306cSNickeau /** 1730*04fd306cSNickeau * @return bool - true if the svg is an icon 1731*04fd306cSNickeau */ 1732*04fd306cSNickeau public function isIconStructure(): bool 1733*04fd306cSNickeau { 1734*04fd306cSNickeau return $this->getInternalStructureType() === self::ICON_TYPE; 1735*04fd306cSNickeau } 1736*04fd306cSNickeau 1737*04fd306cSNickeau /** 1738*04fd306cSNickeau * @return string - the internal structure of the svg 1739*04fd306cSNickeau * of {@link self::ICON_TYPE} or {@link self::ILLUSTRATION_TYPE} 1740*04fd306cSNickeau */ 1741*04fd306cSNickeau private function getInternalStructureType(): string 1742*04fd306cSNickeau { 1743*04fd306cSNickeau 1744*04fd306cSNickeau $mediaWidth = $this->getIntrinsicWidth(); 1745*04fd306cSNickeau $mediaHeight = $this->getIntrinsicHeight(); 1746*04fd306cSNickeau 1747*04fd306cSNickeau if ( 1748*04fd306cSNickeau $mediaWidth == $mediaHeight 1749*04fd306cSNickeau && $mediaWidth < 400) // 356 for logos telegram are the size of the twitter emoji but tile may be bigger ? 1750*04fd306cSNickeau { 1751*04fd306cSNickeau return FetcherSvg::ICON_TYPE; 1752*04fd306cSNickeau } else { 1753*04fd306cSNickeau $svgStructureType = FetcherSvg::ILLUSTRATION_TYPE; 1754*04fd306cSNickeau 1755*04fd306cSNickeau // some icon may be bigger 1756*04fd306cSNickeau // in size than 400. example 1024 for ant-design:table-outlined 1757*04fd306cSNickeau // https://github.com/ant-design/ant-design-icons/blob/master/packages/icons-svg/svg/outlined/table.svg 1758*04fd306cSNickeau // or not squared 1759*04fd306cSNickeau // if the usage is determined or the svg is in the icon directory, it just takes over. 1760*04fd306cSNickeau try { 1761*04fd306cSNickeau $isInIconDirectory = IconDownloader::isInIconDirectory($this->getSourcePath()); 1762*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1763*04fd306cSNickeau // not a svg from a path 1764*04fd306cSNickeau $isInIconDirectory = false; 1765*04fd306cSNickeau } 1766*04fd306cSNickeau try { 1767*04fd306cSNickeau $requestType = $this->getRequestedType(); 1768*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1769*04fd306cSNickeau $requestType = false; 1770*04fd306cSNickeau } 1771*04fd306cSNickeau 1772*04fd306cSNickeau if ($requestType === FetcherSvg::ICON_TYPE || $isInIconDirectory) { 1773*04fd306cSNickeau $svgStructureType = FetcherSvg::ICON_TYPE; 1774*04fd306cSNickeau } 1775*04fd306cSNickeau 1776*04fd306cSNickeau return $svgStructureType; 1777*04fd306cSNickeau 1778*04fd306cSNickeau } 1779*04fd306cSNickeau } 1780*04fd306cSNickeau 1781*04fd306cSNickeau /** 1782*04fd306cSNickeau * 1783*04fd306cSNickeau * This function returns a consistent requested width and height for icon and tile 1784*04fd306cSNickeau * 1785*04fd306cSNickeau * @throws ExceptionNotFound - if not a icon or tile requested 1786*04fd306cSNickeau */ 1787*04fd306cSNickeau private function getDefaultWidhtAndHeightForIconAndTileIfNotSet(): int 1788*04fd306cSNickeau { 1789*04fd306cSNickeau 1790*04fd306cSNickeau if (!$this->norWidthNorHeightWasRequested()) { 1791*04fd306cSNickeau throw new ExceptionNotFound(); 1792*04fd306cSNickeau } 1793*04fd306cSNickeau 1794*04fd306cSNickeau if ($this->isCropRequested()) { 1795*04fd306cSNickeau /** 1796*04fd306cSNickeau * With a crop, the internal dimension takes over 1797*04fd306cSNickeau */ 1798*04fd306cSNickeau throw new ExceptionNotFound(); 1799*04fd306cSNickeau } 1800*04fd306cSNickeau 1801*04fd306cSNickeau $internalStructure = $this->getInternalStructureType(); 1802*04fd306cSNickeau switch ($internalStructure) { 1803*04fd306cSNickeau case FetcherSvg::ICON_TYPE: 1804*04fd306cSNickeau try { 1805*04fd306cSNickeau $requestedType = $this->getRequestedType(); 1806*04fd306cSNickeau } catch (ExceptionNotFound $e) { 1807*04fd306cSNickeau $requestedType = FetcherSvg::ICON_TYPE; 1808*04fd306cSNickeau } 1809*04fd306cSNickeau switch ($requestedType) { 1810*04fd306cSNickeau case FetcherSvg::TILE_TYPE: 1811*04fd306cSNickeau return self::DEFAULT_TILE_WIDTH; 1812*04fd306cSNickeau default: 1813*04fd306cSNickeau case FetcherSvg::ICON_TYPE: 1814*04fd306cSNickeau return FetcherSvg::DEFAULT_ICON_LENGTH; 1815*04fd306cSNickeau } 1816*04fd306cSNickeau default: 1817*04fd306cSNickeau throw new ExceptionNotFound(); 1818*04fd306cSNickeau } 1819*04fd306cSNickeau 1820*04fd306cSNickeau 1821*04fd306cSNickeau } 1822*04fd306cSNickeau 1823*04fd306cSNickeau 1824*04fd306cSNickeau} 1825