<?php declare(strict_types=1); namespace Armin\Vieweg\ViewHelpers; use TYPO3\CMS\Core\Core\Environment; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface; use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper; /** * Font Awesome SVG ViewHelper * * Usage examples: * * <av:fa icon="fab typo3" /> * <av:fa icon="fas file" /> == <av:fa icon="file" /> * <av:fa icon="far file" /> */ class FaViewHelper extends AbstractViewHelper { protected $escapeOutput = false; protected static $cache = []; public function initializeArguments() { $this->registerArgument('icon', 'string', 'Icon name', true); $this->registerArgument('size', 'string', 'Icon size in pixel', false, ''); $this->registerArgument('class', 'string', 'Optional class name(s)', false, ''); $this->registerArgument('color', 'string', 'Optional color', false, ''); } public static function renderStatic( array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext ) { // Prepare requested icon (type and icon (name)) $iconParts = GeneralUtility::trimExplode(' ', $arguments['icon'], true); $type = 'solid'; if (count($iconParts) === 2) { $firstPart = reset($iconParts); if ($firstPart === 'far') { $type = 'regular'; } elseif ($firstPart === 'fab') { $type = 'brands'; } elseif ($firstPart === 'fas') { $type = 'solid'; } else { throw new \InvalidArgumentException( 'Font Awesome icon prefix "' . $firstPart . '" not allowed! Allowed are: "far", "fas" or "fab"' ); } $icon = end($iconParts); } elseif (count($iconParts) === 1) { $icon = reset($iconParts); } else { throw new \InvalidArgumentException( 'Invalid icon name given. Valid name would be: "fas fa-file" or "fa-file" or just "file"' ); } if (strpos($icon, 'fa-') === 0) { $icon = substr($icon, 3); } // Check if icon exists $path = Environment::getProjectPath() . '/vendor/fortawesome/font-awesome/svgs/'; $path .= $type . '/'; $path .= $icon . '.svg'; if (!file_exists($path)) { throw new \RuntimeException('Given Font Awesome Icon "' . $arguments['icon'] . '" not found!'); } // Prepare view helper arguments $size = (string) $arguments['size']; $color = (string) $arguments['color']; $class = 'svg-icon'; if (!empty($arguments['class'])) { $class .= ' ' . $arguments['class']; } // If the same icon is requested a second time, use a reference to symbol instead if (array_key_exists($path, self::$cache)) { $id = self::$cache[$path]; return self::buildSvgSymbolReference($id, $size, $class, $color); } // Create and cache symbol return self::createSvgSymbol($type, $icon, $path, $size, $class, $color); } protected static function buildSvgSymbolReference(string $id, string $size, string $class, string $color): string { if (!empty($size)) { $size = ' width="' . $size . '" height="' . $size . '" '; } if (!empty($color)) { $color = ' fill="' . $color . '"'; } return '<svg' . $size . $color . ' class="' . $class . '">' . '<use xlink:href="#' . $id . '"></use>' . '</svg>'; } /** * @param string $type * @param string $icon * @param string $path * @param string $size * @param string $class * @param string $color * @return string */ protected static function createSvgSymbol( string $type, string $icon, string $path, string $size, string $class, string $color ) : string { $svgContents = file_get_contents($path); $svgDocument = new \DOMDocument(); $svgDocument->loadXML($svgContents); // Used for caching and symbol identifier $id = 'fa-' . $type . '-' . $icon; // Create the symbol $symbolDocument = new \DOMDocument(); $symbol = $symbolDocument->createElement('symbol'); $symbol->setAttribute('id', $id); $symbol->setAttribute('viewBox', $svgDocument->documentElement->getAttribute('viewBox')); $symbolDocument->appendChild($symbol); // Get paths of font awesome SVG foreach ($svgDocument->documentElement->childNodes as $svgpath) { $iconPathsFragment = $symbolDocument->createDocumentFragment(); $iconPathsFragment->appendXML($svgDocument->saveXML($svgpath)); $symbol->appendChild($iconPathsFragment); } // Prepare symbol output $result = '<svg class="d-none">'; foreach ($symbolDocument->childNodes as $childNode) { $result .= $symbolDocument->saveXML($childNode); } $result .= '</svg>'; // Directly append a reference to this symbol (which is never displayed) $result .= self::buildSvgSymbolReference($id, (string)$size, $class, $color); // Add cache item and return the output self::$cache[$path] = $id; return $result; } }