* */ namespace ComboStrap; require_once(__DIR__ . '/MediaLink.php'); require_once(__DIR__ . '/PluginUtility.php'); require_once(__DIR__ . '/SvgDocument.php'); /** * Image * This is the class that handles the * svg link type */ class SvgImageLink extends MediaLink { const CANONICAL = "svg"; /** * The maximum size to be embedded * Above this size limit they are fetched */ const CONF_MAX_KB_SIZE_FOR_INLINE_SVG = "svgMaxInlineSizeKb"; /** * Lazy Load */ const CONF_LAZY_LOAD_ENABLE = "svgLazyLoadEnable"; /** * Svg Injection */ const CONF_SVG_INJECTION_ENABLE = "svgInjectionEnable"; /** * @var SvgDocument */ private $svgDocument; /** * SvgImageLink constructor. * @param $ref * @param TagAttributes $tagAttributes * @param string $rev */ public function __construct($ref, $tagAttributes = null, $rev = '') { parent::__construct($ref, $tagAttributes, $rev); $this->getTagAttributes()->setLogicalTag(self::CANONICAL); } private function createImgHTMLTag() { $lazyLoad = $this->getLazyLoad(); $svgInjection = PluginUtility::getConfValue(self::CONF_SVG_INJECTION_ENABLE, 1); /** * Snippet */ if ($svgInjection) { $snippetManager = PluginUtility::getSnippetManager(); // Based on https://github.com/iconic/SVGInjector/ // See also: https://github.com/iconfu/svg-inject // !! There is a fork: https://github.com/tanem/svg-injector !! // Fallback ? : https://github.com/iconic/SVGInjector/#per-element-png-fallback $snippetManager->upsertTagsForBar("svg-injector", array( 'script' => [ array( "src" => "https://cdn.jsdelivr.net/npm/svg-injector@1.1.3/svg-injector.min.js", // "integrity" => "sha256-CjBlJvxqLCU2HMzFunTelZLFHCJdqgDoHi/qGJWdRJk=", "crossorigin" => "anonymous" ) ] ) ); } // Add lazy load snippet if ($lazyLoad) { LazyLoad::addLozadSnippet(); } /** * Remove the cache attribute * (no cache for the img tag) */ $this->tagAttributes->removeComponentAttributeIfPresent(CacheMedia::CACHE_KEY); /** * Remove linking (not yet implemented) */ $this->tagAttributes->removeComponentAttributeIfPresent(MediaLink::LINKING_KEY); /** * Src */ $srcValue = $this->getUrl(); if ($lazyLoad) { /** * Note: Responsive image srcset is not needed for svg */ $this->tagAttributes->addHtmlAttributeValue("data-src", $srcValue); $this->tagAttributes->addHtmlAttributeValue("src", LazyLoad::getPlaceholder($this->getImgTagWidthValue(), $this->getImgTagHeightValue())); } else { $this->tagAttributes->addHtmlAttributeValue("src", $srcValue); } /** * Adaptive Image * It adds a `height: auto` that avoid a layout shift when * using the img tag */ $this->tagAttributes->addClassName(RasterImageLink::RESPONSIVE_CLASS); /** * Title */ if (!empty($this->getTitle())) { $this->tagAttributes->addHtmlAttributeValue("alt", $this->getTitle()); } /** * Class management * * functionalClass is the class used in Javascript * that should be in the class attribute * When injected, the other class should come in a `data-class` attribute */ $svgFunctionalClass = ""; if ($svgInjection && $lazyLoad) { PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("lozad-svg-injection"); $svgFunctionalClass = "lazy-svg-injection-combo"; } else if ($lazyLoad && !$svgInjection) { PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("lozad-svg"); $svgFunctionalClass = "lazy-svg-combo"; } else if ($svgInjection && !$lazyLoad) { PluginUtility::getSnippetManager()->attachJavascriptSnippetForBar("svg-injector"); $svgFunctionalClass = "svg-injection-combo"; } if ($lazyLoad) { // A class to all component lazy loaded to download them before print $svgFunctionalClass .= " " . LazyLoad::LAZY_CLASS; } $this->tagAttributes->addClassName($svgFunctionalClass); /** * Dimension are mandatory * to avoid layout shift (CLS) */ $this->tagAttributes->addHtmlAttributeValue(Dimension::WIDTH_KEY, $this->getImgTagWidthValue()); $this->tagAttributes->addHtmlAttributeValue(Dimension::HEIGHT_KEY, $this->getImgTagHeightValue()); /** * Return the image */ return 'tagAttributes->toHTMLAttributeString() . '/>'; } public function getAbsoluteUrl() { return $this->getUrl(); } /** * @param string $ampersand $absolute - the & separator (should be encoded for HTML but not for CSS) * @return string|null * * At contrary to {@link RasterImageLink::getUrl()} this function does not need any width parameter */ public function getUrl($ampersand = DokuwikiUrl::URL_ENCODED_AND) { if ($this->exists()) { /** * We remove align and linking because, * they should apply only to the img tag */ /** * * Create the array $att that will cary the query * parameter for the URL */ $att = array(); $componentAttributes = $this->tagAttributes->getComponentAttributes(); foreach ($componentAttributes as $name => $value) { if (!in_array(strtolower($name), MediaLink::NON_URL_ATTRIBUTES)) { $newName = $name; /** * Width and Height * permits to create SVG of the asked size * * This is a little bit redundant with the * {@link Dimension::processWidthAndHeight()} * `max-width and width` styling property * but you may use them outside of HTML. */ switch ($name) { case Dimension::WIDTH_KEY: $newName = "w"; /** * We don't remove width because, * the sizing should apply to img */ break; case Dimension::HEIGHT_KEY: $newName = "h"; /** * We don't remove height because, * the sizing should apply to img */ break; } if ($newName == CacheMedia::CACHE_KEY && $value == CacheMedia::CACHE_DEFAULT_VALUE) { // This is the default // No need to add it continue; } if (!empty($value)) { $att[$newName] = trim($value); } } } /** * Cache bursting */ if (!$this->tagAttributes->hasComponentAttribute(CacheMedia::CACHE_BUSTER_KEY)) { $att[CacheMedia::CACHE_BUSTER_KEY] = $this->getModifiedTime(); } $direct = true; return ml($this->getId(), $att, $direct, $ampersand, true); } else { return null; } } /** * Render a link * Snippet derived from {@link \Doku_Renderer_xhtml::internalmedia()} * A media can be a video also * @return string */ public function renderMediaTag() { if ($this->exists()) { /** * This attributes should not be in the render */ $this->tagAttributes->removeComponentAttributeIfPresent(MediaLink::MEDIA_DOKUWIKI_TYPE); $this->tagAttributes->removeComponentAttributeIfPresent(MediaLink::DOKUWIKI_SRC); /** * TODO: Title should be a node just below SVG */ $this->tagAttributes->removeComponentAttributeIfPresent(Page::TITLE_META_PROPERTY); if ( $this->getSize() > $this->getMaxInlineSize() ) { /** * Img tag */ $imgHTML = $this->createImgHTMLTag(); } else { /** * Svg tag */ $imgHTML = file_get_contents($this->getSvgFile()); } } else { $imgHTML = "The svg ($this) does not exist"; } return $imgHTML; } private function getMaxInlineSize() { return PluginUtility::getConfValue(self::CONF_MAX_KB_SIZE_FOR_INLINE_SVG, 2) * 1024; } public function getLazyLoad() { $lazyLoad = parent::getLazyLoad(); if ($lazyLoad !== null) { return $lazyLoad; } else { return PluginUtility::getConfValue(SvgImageLink::CONF_LAZY_LOAD_ENABLE); } } public function getSvgFile() { $cache = new CacheMedia($this, $this->tagAttributes); if (!$cache->isCacheUsable()) { $content = $this->getSvgDocument()->getXmlText($this->tagAttributes); $cache->storeCache($content); } return $cache->getFile()->getFileSystemPath(); } public function getMediaWidth() { return $this->getSvgDocument()->getMediaWidth(); } public function getMediaHeight() { return $this->getSvgDocument()->getMediaHeight(); } private function getSvgDocument() { if ($this->svgDocument == null) { $this->svgDocument = SvgDocument::createFromPath($this); } return $this->svgDocument; } }