xref: /template/strap/ComboStrap/FetcherSvg.php (revision 70bbd7f1f72440223cc13f3495efdcb2b0a11514)
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