Skip to content
Snippets Groups Projects
Commit f98cb4cb authored by Johannes Kreiner's avatar Johannes Kreiner
Browse files

[TASK] Update RenderSvgViewHelper

parent ef538968
No related branches found
No related tags found
No related merge requests found
......@@ -7,7 +7,7 @@
*
* All rights reserved
*
* This script is part of the AY project. The AY project is
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
......@@ -26,6 +26,8 @@
namespace SGalinski\SgYoutube\ViewHelpers;
use Exception;
use SimpleXMLElement;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper;
/**
......@@ -39,7 +41,18 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper {
* @api
*/
protected $tagName = 'svg';
protected $xmlInfo = '<?xml version="1.0"?>';
/**
* @var array
*/
protected static array $svgInstances = [];
/**
* The directory path where the SVGs are located
*
* @var string
*/
protected $directoryPath = __DIR__ . '/../../Resources/Public/Icons/';
/**
......@@ -47,39 +60,77 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper {
*/
public function initializeArguments(): void {
parent::initializeArguments();
$this->registerArgument('name', 'string', 'The SVG name', TRUE);
$this->registerArgument('color', 'string', 'The fill color', TRUE);
$this->registerArgument('id', 'string', 'The HTML id attribute', FALSE);
$this->registerArgument('path', 'string', 'Path to the SVG file if not in default', FALSE);
$this->registerArgument('class', 'string', 'The HTML class attribute', FALSE);
$this->registerArgument('title', 'string', 'The HTML title attribute', FALSE);
$this->registerArgument('width', 'string', 'The HTML width attribute', FALSE);
$this->registerArgument('height', 'string', 'The HTML height attribute', FALSE);
$this->registerArgument('style', 'string', 'Inline CSS styles', FALSE);
$this->registerArgument('use', 'string', 'Inline CSS styles', FALSE);
$this->registerArgument(
'name',
'string',
'The SVG name, also supports folders (e.g. fontawesome/solid/ad)',
TRUE
);
$this->registerArgument(
'color',
'string',
'The color',
FALSE,
'currentColor'
);
$this->registerArgument(
'colorAttribute',
'string',
'The color target to affect: fill, stroke. Leave empty for both'
);
$this->registerArgument('id', 'string', 'The HTML id attribute');
$this->registerArgument('path', 'string', 'Path to the SVG file if not in default');
$this->registerArgument('class', 'string', 'The HTML class attribute');
$this->registerArgument('title', 'string', 'The HTML title attribute');
$this->registerArgument('width', 'string', 'The HTML width attribute');
$this->registerArgument('height', 'string', 'The HTML height attribute');
$this->registerArgument('viewBox', 'string', 'The HTML viewBox attribute');
$this->registerArgument(
'viewBoxOnly',
'boolean',
'If only the viewBox attribute should be used instead of width/height'
);
$this->registerArgument('stroke-width', 'string', 'The HTML stroke-width attribute');
$this->registerArgument('style', 'string', 'Inline CSS styles');
$this->registerArgument('use', 'string', 'Inline CSS styles');
$this->registerArgument(
'createNewInstance',
'boolean',
'Creates a new instance without reusing other ones'
);
$this->registerArgument('preserveColors', 'boolean', 'Preserves the original colors');
$this->registerArgument(
'createColorAttribute',
'boolean',
'Creates the color target for cases when it doesn\'t exist.',
FALSE,
FALSE
);
}
/**
* Render the SVG file as an inline SVG element.
*
* @return string The rendered SVG element
* @throws \Exception
* @throws Exception
*/
public function render(): string {
global $tx_sgvideo_svgIds;
if (!$tx_sgvideo_svgIds) {
$tx_sgvideo_svgIds = [];
}
$name = $this->arguments['name'];
$path = $this->arguments['path'];
$width = $this->arguments['width'];
$height = $this->arguments['height'];
$viewBox = $this->arguments['viewBox'];
$viewBoxOnly = $this->arguments['viewBoxOnly'];
$strokeWidth = $this->arguments['stroke-width'];
$color = $this->arguments['color'];
$colorAttribute = $this->arguments['colorAttribute'];
$id = $this->arguments['id'];
$class = $this->arguments['class'];
$style = $this->arguments['style'];
$title = $this->arguments['title'];
$createNewInstance = $this->arguments['createNewInstance'];
$preserveColors = $this->arguments['preserveColors'];
$createColorAttribute = $this->arguments['createColorAttribute'];
$src = $path ?? $this->directoryPath . '/' . $name . '.svg';
......@@ -92,7 +143,7 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper {
}
// Load the SVG into a SimpleXMLElement object
$svg = new \SimpleXMLElement($content);
$svg = new SimpleXMLElement($content);
// Set the attributes of the SVG element
if ($width > 0) {
......@@ -103,8 +154,30 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper {
$this->addOrReplaceAttribute($svg, 'height', $height);
}
if (!empty($color)) {
$this->setFill($svg, $color);
if (!empty($viewBox)) {
$this->addOrReplaceAttribute($svg, 'viewBox', $viewBox);
}
if ($viewBoxOnly) {
unset($svg->attributes()->width, $svg->attributes()->height);
}
if ($strokeWidth > 0) {
$this->setStrokeWidth($svg, $strokeWidth);
}
if (!empty($color) && !$preserveColors) {
switch ($colorAttribute) {
case 'fill':
$this->setFill($svg, $color, $createColorAttribute);
break;
case 'stroke':
$this->setStroke($svg, $color, $createColorAttribute);
break;
default:
$this->setFill($svg, $color, $createColorAttribute);
$this->setStroke($svg, $color, $createColorAttribute);
}
}
if ($style) {
......@@ -119,124 +192,106 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper {
$this->addOrReplaceAttribute($svg, 'title', $title);
}
if ($createNewInstance) {
return str_replace($this->xmlInfo, '', $svg->asXML());
}
// Extract the SVG contents
$contents = $this->getContents($svg, TRUE);
// Check if the SVG has already been rendered and use the <use> tag if possible
if (!empty($tx_sgvideo_svgIds)) {
if (isset($tx_sgvideo_svgIds[$id])) {
$use = $svg->addChild('use');
// The boolean conversion of SimpleXMLElement is broken, therefore we MUST use instanceof
if ($use instanceof \SimpleXMLElement) {
$use->addAttribute('href', '#' . $id);
}
return $svg->asXML();
if (isset(static::$svgInstances[$id])) {
$use = $svg->addChild('use');
// The boolean conversion of SimpleXMLElement is broken, therefore we MUST use instanceof
if ($use instanceof SimpleXMLElement) {
$use->addAttribute('href', '#' . $id);
}
} else {
$tx_sgvideo_svgIds = [];
return str_replace($this->xmlInfo, '', $svg->asXML());
}
// Add the unique ID to the list of rendered SVGs
$tx_sgvideo_svgIds[$id] = $id;
static::$svgInstances[$id] = $id;
$contentsElement = new \SimpleXMLElement($contents);
$contentsElement = new SimpleXMLElement($contents);
$group = $svg->addChild('g');
// The boolean conversion of SimpleXMLElement is broken, therefore we MUST explicitly check against null
if ($group === NULL) {
return $svg->asXML();
return str_replace($this->xmlInfo, '', $svg->asXML());
}
/** @noinspection NullPointerExceptionInspection */
$group->addAttribute('id', $id);
$this->xmlAdopt($group, $contentsElement);
return $svg->asXML();
//Todo: we don't really want this, do we?
// if (!empty($width) && !empty($height)) {
// $resultWithoutCssClass = str_replace($cssClass, '', $result);
// $cssClasses = $this->processWrapperClasses($cssClassPlain);
//
// return "<span $cssClasses style='width: $width; height: $height;'>$resultWithoutCssClass</span>";
// }
//
// if (!empty($width)) {
// $resultWithoutCssClass = str_replace($cssClass, '', $result);
// $cssClasses = $this->processWrapperClasses($cssClassPlain);
//
// return "<span $cssClasses style='width: $width;'>$resultWithoutCssClass</span>";
// }
//
// if (!empty($height)) {
// $resultWithoutCssClass = str_replace($cssClass, '', $result);
// $cssClasses = $this->processWrapperClasses($cssClassPlain);
//
// return "<span $cssClasses style='height: $height;'>$resultWithoutCssClass</span>";
// }
//
// return $result;
return str_replace($this->xmlInfo, '', $svg->asXML());
}
/**
* Processes the common SVG wrapper's classes
* Set the fill color of an SVG element.
*
* @param string $cssClass
* @return string
* @param SimpleXMLElement $element The SVG element
* @param string $fill The fill color to set
* @param boolean $createColorAttribute If the attribute should be added
*/
public function processWrapperClasses(string $cssClass): string {
return "class='$cssClass svg-wrapper d-inline-flex justify-content-center align-items-center'";
protected function setFill(SimpleXMLElement $element, string $fill, bool $createColorAttribute): void {
foreach ($element->children() as $child) {
$this->setFill($child, $fill, $createColorAttribute);
}
if ($createColorAttribute) {
$element->addAttribute('fill', $fill);
} elseif (isset($element->attributes()->fill)) {
$element->attributes()->fill = $fill;
}
}
/**
* Processes the wrappers for the usage of the <use> SVG functionality
* Set the stroke color of an SVG element.
*
* @param string $svgContent
* @param string $id
* @param string $use
* @return string
* @param SimpleXMLElement $element The SVG element
* @param string $stroke The stroke color to set
* @param boolean $createColorAttribute If the attribute should be added
*/
public function processUseWrappers(string $svgContent, string $id, string $use): string {
$svgContent = "<g $id>$svgContent</g>";
if ($use) {
$svgContent = "<use href='#$use' />";
protected function setStroke(SimpleXMLElement $element, string $stroke, bool $createColorAttribute): void {
foreach ($element->children() as $child) {
$this->setStroke($child, $stroke, $createColorAttribute);
}
if ($createColorAttribute) {
$element->addAttribute('stroke', $stroke);
} elseif (isset($element->attributes()->stroke)) {
$element->attributes()->stroke = $stroke;
}
return $svgContent;
}
/**
* Set the fill color of an SVG element.
* Set the stroke width of an SVG element.
*
* @param \SimpleXMLElement $element The SVG element
* @param string $fill The fill color to set
* @param SimpleXMLElement $element The SVG element
* @param string $strokeWidth The stroke width to set
*/
protected function setFill(\SimpleXMLElement $element, string $fill): void {
protected function setStrokeWidth(SimpleXMLElement $element, string $strokeWidth): void {
foreach ($element->children() as $child) {
$this->setFill($child, $fill);
$this->setStrokeWidth($child, $strokeWidth);
}
if (isset($element->attributes()->fill)) {
$element->attributes()->fill = $fill;
foreach ($element->path as $path) {
$this->addOrReplaceAttribute($path, 'stroke-width', $strokeWidth);
}
}
/**
* Gets the contents of the SVG file
*
* @param \SimpleXMLElement $svg
* @param SimpleXMLElement $svg
* @param bool $removeNode
* @return string
*/
private function getContents(\SimpleXMLElement $svg, bool $removeNode = FALSE): string {
private function getContents(SimpleXMLElement $svg, bool $removeNode = FALSE): string {
$contents = '';
foreach ($svg->children() as $child) {
$contents .= $child->asXML() . "\n";
if ($removeNode) {
$dom = dom_import_simplexml($child);
if ($dom) {
$dom->parentNode->removeChild($dom);
}
$dom->parentNode->removeChild($dom);
}
}
......@@ -246,20 +301,21 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper {
/**
* Inserts a new SimpleXMLElement at the given root
*
* @param \SimpleXMLElement $root
* @param \SimpleXMLElement $newElement
* @param SimpleXMLElement $root
* @param SimpleXMLElement $newElement
* @return void
*/
private function xmlAdopt(\SimpleXMLElement $root, \SimpleXMLElement $newElement): void {
private function xmlAdopt(SimpleXMLElement $root, SimpleXMLElement $newElement): void {
$node = $root->addChild($newElement->getName(), (string) $newElement);
if ($node === NULL) {
return;
}
foreach ($newElement->attributes() as $attr => $value) {
/** @noinspection NullPointerExceptionInspection */
$node->addAttribute($attr, $value);
}
foreach ($newElement->children() as $ch) {
/** @noinspection NullPointerExceptionInspection */
$this->xmlAdopt($node, $ch);
}
}
......@@ -267,20 +323,21 @@ class RenderSvgViewHelper extends AbstractTagBasedViewHelper {
/**
* Adds or replaces an attribute
*
* @param \SimpleXMLElement $element
* @param SimpleXMLElement $element
* @param string $attributeName
* @param string $attributeValue
* @return void
*/
private function addOrReplaceAttribute(
\SimpleXMLElement $element,
SimpleXMLElement $element,
string $attributeName,
string $attributeValue
): void {
if (isset($element[$attributeName])) {
$element->$attributeName = $attributeValue; // Replace existing attribute value
} else {
$element->addAttribute($attributeName, $attributeValue); // Add new attribute
// Replace existing attribute value
unset($element[$attributeName]);
}
$element->addAttribute($attributeName, $attributeValue);
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment