mirror of
https://github.com/dokuwiki/dokuwiki-plugin-dev.git
synced 2026-01-13 20:12:48 +00:00
230 lines
6.5 KiB
PHP
230 lines
6.5 KiB
PHP
<?php
|
|
|
|
namespace dokuwiki\plugin\dev;
|
|
|
|
use dokuwiki\HTTP\DokuHTTPClient;
|
|
use splitbrain\phpcli\CLI;
|
|
|
|
/**
|
|
* Download and clean SVG icons
|
|
*/
|
|
class SVGIcon
|
|
{
|
|
|
|
const SOURCES = [
|
|
'mdi' => "https://raw.githubusercontent.com/Templarian/MaterialDesign/master/svg/%s.svg",
|
|
'fab' => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/brands/%s.svg",
|
|
'fas' => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/solid/%s.svg",
|
|
'fa' => "https://raw.githubusercontent.com/FortAwesome/Font-Awesome/master/svgs/regular/%s.svg",
|
|
'twbs' => "https://raw.githubusercontent.com/twbs/icons/main/icons/%s.svg",
|
|
];
|
|
|
|
/** @var CLI for logging */
|
|
protected $logger;
|
|
|
|
/** @var bool keep the SVG namespace for when the image is not used in embed? */
|
|
protected $keepns = false;
|
|
|
|
/**
|
|
* @throws \Exception
|
|
*/
|
|
public function __construct(CLI $logger)
|
|
{
|
|
$this->logger = $logger;
|
|
}
|
|
|
|
/**
|
|
* Call before cleaning to keep the SVG namespace
|
|
*
|
|
* @param bool $keep
|
|
*/
|
|
public function keepNamespace($keep = true)
|
|
{
|
|
$this->keepns = $keep;
|
|
}
|
|
|
|
/**
|
|
* Download and save a remote icon
|
|
*
|
|
* @param string $ident prefixed name of the icon
|
|
* @param string $save
|
|
* @return bool
|
|
* @throws \Exception
|
|
*/
|
|
public function downloadRemoteIcon($ident, $save = '')
|
|
{
|
|
$icon = $this->remoteIcon($ident);
|
|
$svgdata = $this->fetchSVG($icon['url']);
|
|
$svgdata = $this->cleanSVG($svgdata);
|
|
|
|
if (!$save) {
|
|
$save = $icon['name'] . '.svg';
|
|
}
|
|
|
|
io_makeFileDir($save);
|
|
$ok = io_saveFile($save, $svgdata);
|
|
if ($ok) $this->logger->success('saved ' . $save);
|
|
return $ok;
|
|
}
|
|
|
|
/**
|
|
* Clean an existing SVG file
|
|
*
|
|
* @param string $file
|
|
* @return bool
|
|
* @throws \Exception
|
|
*/
|
|
public function cleanSVGFile($file)
|
|
{
|
|
$svgdata = io_readFile($file, false);
|
|
if (!$svgdata) {
|
|
throw new \Exception('Failed to read ' . $file);
|
|
}
|
|
|
|
$svgdata = $this->cleanSVG($svgdata);
|
|
$ok = io_saveFile($file, $svgdata);
|
|
if ($ok) $this->logger->success('saved ' . $file);
|
|
return $ok;
|
|
}
|
|
|
|
/**
|
|
* Get info about an icon from a known remote repository
|
|
*
|
|
* @param string $ident prefixed name of the icon
|
|
* @return array
|
|
* @throws \Exception
|
|
*/
|
|
public function remoteIcon($ident)
|
|
{
|
|
if (strpos($ident, ':')) {
|
|
list($prefix, $name) = explode(':', $ident);
|
|
} else {
|
|
$prefix = 'mdi';
|
|
$name = $ident;
|
|
}
|
|
if (!isset(self::SOURCES[$prefix])) {
|
|
throw new \Exception("Unknown prefix $prefix");
|
|
}
|
|
|
|
$url = sprintf(self::SOURCES[$prefix], $name);
|
|
|
|
return [
|
|
'prefix' => $prefix,
|
|
'name' => $name,
|
|
'url' => $url,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Minify SVG
|
|
*
|
|
* @param string $svgdata
|
|
* @return string
|
|
*/
|
|
protected function cleanSVG($svgdata)
|
|
{
|
|
$old = strlen($svgdata);
|
|
|
|
// strip namespace declarations FIXME is there a cleaner way?
|
|
$svgdata = preg_replace('/\sxmlns(:.*?)?="(.*?)"/', '', $svgdata);
|
|
|
|
$dom = new \DOMDocument();
|
|
$dom->loadXML($svgdata, LIBXML_NOBLANKS);
|
|
$dom->formatOutput = false;
|
|
$dom->preserveWhiteSpace = false;
|
|
|
|
$svg = $dom->getElementsByTagName('svg')->item(0);
|
|
|
|
// prefer viewbox over width/height
|
|
if (!$svg->hasAttribute('viewBox')) {
|
|
$w = $svg->getAttribute('width');
|
|
$h = $svg->getAttribute('height');
|
|
if ($w && $h) {
|
|
$svg->setAttribute('viewBox', "0 0 $w $h");
|
|
}
|
|
}
|
|
|
|
// remove unwanted attributes from root
|
|
$this->removeAttributes($svg, ['viewBox']);
|
|
|
|
// remove unwanted attributes from primitives
|
|
foreach ($dom->getElementsByTagName('path') as $elem) {
|
|
$this->removeAttributes($elem, ['d']);
|
|
}
|
|
foreach ($dom->getElementsByTagName('rect') as $elem) {
|
|
$this->removeAttributes($elem, ['x', 'y', 'rx', 'ry']);
|
|
}
|
|
foreach ($dom->getElementsByTagName('circle') as $elem) {
|
|
$this->removeAttributes($elem, ['cx', 'cy', 'r']);
|
|
}
|
|
foreach ($dom->getElementsByTagName('ellipse') as $elem) {
|
|
$this->removeAttributes($elem, ['cx', 'cy', 'rx', 'ry']);
|
|
}
|
|
foreach ($dom->getElementsByTagName('line') as $elem) {
|
|
$this->removeAttributes($elem, ['x1', 'x2', 'y1', 'y2']);
|
|
}
|
|
foreach ($dom->getElementsByTagName('polyline') as $elem) {
|
|
$this->removeAttributes($elem, ['points']);
|
|
}
|
|
foreach ($dom->getElementsByTagName('polygon') as $elem) {
|
|
$this->removeAttributes($elem, ['points']);
|
|
}
|
|
|
|
// remove comments see https://stackoverflow.com/a/60420210
|
|
$xpath = new \DOMXPath($dom);
|
|
for ($els = $xpath->query('//comment()'), $i = $els->length - 1; $i >= 0; $i--) {
|
|
$els->item($i)->parentNode->removeChild($els->item($i));
|
|
}
|
|
|
|
// readd namespace if not meant for embedding
|
|
if ($this->keepns) {
|
|
$svg->setAttribute('xmlns', 'http://www.w3.org/2000/svg');
|
|
}
|
|
|
|
$svgdata = $dom->saveXML($svg);
|
|
$new = strlen($svgdata);
|
|
|
|
$this->logger->info(sprintf('Minified SVG %d bytes -> %d bytes (%.2f%%)', $old, $new, $new * 100 / $old));
|
|
if ($new > 2048) {
|
|
$this->logger->warning('%d bytes is still too big for standard inlineSVG() limit!');
|
|
}
|
|
return $svgdata;
|
|
}
|
|
|
|
/**
|
|
* Remove all attributes except the given keepers
|
|
*
|
|
* @param \DOMNode $element
|
|
* @param string[] $keep
|
|
*/
|
|
protected function removeAttributes($element, $keep)
|
|
{
|
|
$attributes = $element->attributes;
|
|
for ($i = $attributes->length - 1; $i >= 0; $i--) {
|
|
$name = $attributes->item($i)->name;
|
|
if (in_array($name, $keep)) continue;
|
|
$element->removeAttribute($name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch the content from the given URL
|
|
*
|
|
* @param string $url
|
|
* @return string
|
|
* @throws \Exception
|
|
*/
|
|
protected function fetchSVG($url)
|
|
{
|
|
$http = new DokuHTTPClient();
|
|
$svg = $http->get($url);
|
|
|
|
if (!$svg) {
|
|
throw new \Exception("Failed to download $url: " . $http->status . ' ' . $http->error);
|
|
}
|
|
|
|
return $svg;
|
|
}
|
|
|
|
}
|