xref: /template/sprintdoc/svg.php (revision 24ab1f725c47400c809211b7ebf1197468d8252d)
11072ee52SAndreas Gohr<?php
21072ee52SAndreas Gohr
31072ee52SAndreas Gohrnamespace dokuwiki\template\sprintdoc;
41072ee52SAndreas Gohr
51072ee52SAndreas Gohrif(!defined('DOKU_INC')) define('DOKU_INC', dirname(__FILE__) . '/../../../');
61072ee52SAndreas Gohrrequire_once(DOKU_INC . 'inc/init.php');
71072ee52SAndreas Gohr
81072ee52SAndreas Gohr/**
91072ee52SAndreas Gohr * Custom XML node that allows prepending
101072ee52SAndreas Gohr */
111072ee52SAndreas Gohrclass SvgNode extends \SimpleXMLElement {
121072ee52SAndreas Gohr    /**
131072ee52SAndreas Gohr     * @param string $name Name of the new node
141072ee52SAndreas Gohr     * @param null|string $value
151072ee52SAndreas Gohr     * @return SvgNode
161072ee52SAndreas Gohr     */
171072ee52SAndreas Gohr    public function prependChild($name, $value = null) {
181072ee52SAndreas Gohr        $dom = dom_import_simplexml($this);
191072ee52SAndreas Gohr
201072ee52SAndreas Gohr        $new = $dom->insertBefore(
211072ee52SAndreas Gohr            $dom->ownerDocument->createElement($name, $value),
221072ee52SAndreas Gohr            $dom->firstChild
231072ee52SAndreas Gohr        );
241072ee52SAndreas Gohr
251072ee52SAndreas Gohr        return simplexml_import_dom($new, get_class($this));
261072ee52SAndreas Gohr    }
2780d784e1SMichael Grosse
2880d784e1SMichael Grosse    /**
2980d784e1SMichael Grosse     * @param \SimpleXMLElement $node the node to be added
3080d784e1SMichael Grosse     * @return \SimpleXMLElement
3180d784e1SMichael Grosse     */
3280d784e1SMichael Grosse    public function appendNode(\SimpleXMLElement $node) {
3380d784e1SMichael Grosse        $dom = dom_import_simplexml($this);
3480d784e1SMichael Grosse        $domNode = dom_import_simplexml($node);
3580d784e1SMichael Grosse
3680d784e1SMichael Grosse        $newNode = $dom->appendChild($domNode);
3780d784e1SMichael Grosse        return simplexml_import_dom($newNode, get_class($this));
3880d784e1SMichael Grosse    }
3980d784e1SMichael Grosse
4080d784e1SMichael Grosse    /**
4180d784e1SMichael Grosse     * @param \SimpleXMLElement $node the child to remove
4280d784e1SMichael Grosse     * @return \SimpleXMLElement
4380d784e1SMichael Grosse     */
4480d784e1SMichael Grosse    public function removeChild(\SimpleXMLElement $node) {
4580d784e1SMichael Grosse        $dom = dom_import_simplexml($node);
4680d784e1SMichael Grosse        $dom->parentNode->removeChild($dom);
4780d784e1SMichael Grosse        return $node;
4880d784e1SMichael Grosse    }
49*24ab1f72SAndreas Gohr
50*24ab1f72SAndreas Gohr    /**
51*24ab1f72SAndreas Gohr     * Wraps all elements of $this in a `<g>` tag
52*24ab1f72SAndreas Gohr     *
53*24ab1f72SAndreas Gohr     * @return SvgNode
54*24ab1f72SAndreas Gohr     */
55*24ab1f72SAndreas Gohr    public function groupChildren() {
56*24ab1f72SAndreas Gohr        $dom = dom_import_simplexml($this);
57*24ab1f72SAndreas Gohr
58*24ab1f72SAndreas Gohr        $g = $dom->ownerDocument->createElement('g');
59*24ab1f72SAndreas Gohr        while ($dom->childNodes->length > 0) {
60*24ab1f72SAndreas Gohr            $child = $dom->childNodes->item(0);
61*24ab1f72SAndreas Gohr            $dom->removeChild($child);
62*24ab1f72SAndreas Gohr            $g->appendChild($child);
63*24ab1f72SAndreas Gohr        }
64*24ab1f72SAndreas Gohr        $g = $dom->appendChild($g);
65*24ab1f72SAndreas Gohr
66*24ab1f72SAndreas Gohr        return simplexml_import_dom($g, get_class($this));
67*24ab1f72SAndreas Gohr    }
68*24ab1f72SAndreas Gohr
69*24ab1f72SAndreas Gohr    /**
70*24ab1f72SAndreas Gohr     * Add new style definitions to this element
71*24ab1f72SAndreas Gohr     * @param string $style
72*24ab1f72SAndreas Gohr     */
73*24ab1f72SAndreas Gohr    public function addStyle($style) {
74*24ab1f72SAndreas Gohr        $defs = $this->defs;
75*24ab1f72SAndreas Gohr        if(!$defs) {
76*24ab1f72SAndreas Gohr            $defs = $this->prependChild('defs');
77*24ab1f72SAndreas Gohr        }
78*24ab1f72SAndreas Gohr        $defs->addChild('style', $style);
79*24ab1f72SAndreas Gohr    }
801072ee52SAndreas Gohr}
811072ee52SAndreas Gohr
821072ee52SAndreas Gohr/**
831072ee52SAndreas Gohr * Manage SVG recoloring
841072ee52SAndreas Gohr */
851072ee52SAndreas Gohrclass SVG {
861072ee52SAndreas Gohr
871072ee52SAndreas Gohr    const IMGDIR = __DIR__ . '/img/';
8880d784e1SMichael Grosse    const BACKGROUNDCLASS = 'sprintdoc-background';
891072ee52SAndreas Gohr
90*24ab1f72SAndreas Gohr    protected $file;
911072ee52SAndreas Gohr
921072ee52SAndreas Gohr    /**
931072ee52SAndreas Gohr     * SVG constructor
941072ee52SAndreas Gohr     */
951072ee52SAndreas Gohr    public function __construct() {
961072ee52SAndreas Gohr        global $INPUT;
971072ee52SAndreas Gohr
981072ee52SAndreas Gohr        $svg = cleanID($INPUT->str('svg'));
991072ee52SAndreas Gohr        if(blank($svg)) $this->abort(404);
1001072ee52SAndreas Gohr
1011072ee52SAndreas Gohr        // try local file first
1021072ee52SAndreas Gohr        $file = self::IMGDIR . $svg;
1031072ee52SAndreas Gohr        if(!file_exists($file)) {
1041072ee52SAndreas Gohr            // media files are ACL protected
1053ec07d58SAndreas Gohr            if(auth_quickaclcheck($svg) < AUTH_READ) $this->abort(403);
1061072ee52SAndreas Gohr            $file = mediaFN($svg);
1071072ee52SAndreas Gohr        }
1081072ee52SAndreas Gohr        // check if media exists
1091072ee52SAndreas Gohr        if(!file_exists($file)) $this->abort(404);
1101072ee52SAndreas Gohr
111*24ab1f72SAndreas Gohr        $this->file = $file;
1121072ee52SAndreas Gohr    }
1131072ee52SAndreas Gohr
1141072ee52SAndreas Gohr    /**
1151072ee52SAndreas Gohr     * Generate and output
1161072ee52SAndreas Gohr     */
1171072ee52SAndreas Gohr    public function out() {
118*24ab1f72SAndreas Gohr        $file = $this->file;
119*24ab1f72SAndreas Gohr        $params = $this->getParameters();
120*24ab1f72SAndreas Gohr
1214fd6492bSAndreas Gohr        header('Content-Type: image/svg+xml');
122*24ab1f72SAndreas Gohr        $cachekey = md5($file . serialize($params));
123*24ab1f72SAndreas Gohr        $cache = new \cache($cachekey, '.svg');
124*24ab1f72SAndreas Gohr        $cache->_event = 'SVG_CACHE';
125*24ab1f72SAndreas Gohr
126*24ab1f72SAndreas Gohr        http_cached($cache->cache, $cache->useCache(array('files' => array($file, __FILE__))));
127*24ab1f72SAndreas Gohr        http_cached_finish($cache->cache, $this->generateSVG($file, $params));
1281072ee52SAndreas Gohr    }
1291072ee52SAndreas Gohr
1301072ee52SAndreas Gohr    /**
131*24ab1f72SAndreas Gohr     * Generate a new SVG based on the input file and the parameters
1321072ee52SAndreas Gohr     *
133*24ab1f72SAndreas Gohr     * @param string $file the SVG file to load
134*24ab1f72SAndreas Gohr     * @param array $params the parameters as returned by getParameters()
135*24ab1f72SAndreas Gohr     * @return string the new XML contents
1361072ee52SAndreas Gohr     */
137*24ab1f72SAndreas Gohr    protected function generateSVG($file, $params) {
138*24ab1f72SAndreas Gohr        /** @var SvgNode $xml */
139*24ab1f72SAndreas Gohr        $xml = simplexml_load_file($file, SvgNode::class);
140*24ab1f72SAndreas Gohr        $xml->addStyle($this->makeStyle($params));
141*24ab1f72SAndreas Gohr        $this->createBackground($xml);
142*24ab1f72SAndreas Gohr        $xml->groupChildren();
143*24ab1f72SAndreas Gohr
144*24ab1f72SAndreas Gohr        return $xml->asXML();
145*24ab1f72SAndreas Gohr    }
146*24ab1f72SAndreas Gohr
147*24ab1f72SAndreas Gohr    /**
148*24ab1f72SAndreas Gohr     * Get the supported parameters from request
149*24ab1f72SAndreas Gohr     *
150*24ab1f72SAndreas Gohr     * @return array
151*24ab1f72SAndreas Gohr     */
152*24ab1f72SAndreas Gohr    protected function getParameters() {
1531072ee52SAndreas Gohr        global $INPUT;
1541072ee52SAndreas Gohr
155*24ab1f72SAndreas Gohr        $params = array(
1561072ee52SAndreas Gohr            's' => $this->fixColor($INPUT->str('s')),
1571072ee52SAndreas Gohr            'f' => $this->fixColor($INPUT->str('f')),
15880d784e1SMichael Grosse            'b' => $this->fixColor($INPUT->str('b')),
1591072ee52SAndreas Gohr            'sh' => $this->fixColor($INPUT->str('sh')),
1601072ee52SAndreas Gohr            'fh' => $this->fixColor($INPUT->str('fh')),
16180d784e1SMichael Grosse            'bh' => $this->fixColor($INPUT->str('bh')),
1621072ee52SAndreas Gohr        );
1631072ee52SAndreas Gohr
164*24ab1f72SAndreas Gohr        return $params;
16580d784e1SMichael Grosse    }
16680d784e1SMichael Grosse
167*24ab1f72SAndreas Gohr    /**
168*24ab1f72SAndreas Gohr     * Generate a style setting from the input variables
169*24ab1f72SAndreas Gohr     *
170*24ab1f72SAndreas Gohr     * @param array $params associative array with the given parameters
171*24ab1f72SAndreas Gohr     * @return string
172*24ab1f72SAndreas Gohr     */
173*24ab1f72SAndreas Gohr    protected function makeStyle($params) {
174*24ab1f72SAndreas Gohr        $element = 'path'; // FIXME configurable?
17580d784e1SMichael Grosse
176*24ab1f72SAndreas Gohr        if(empty($params['b'])) {
177*24ab1f72SAndreas Gohr            $params['b'] = $this->fixColor('00000000');
17880d784e1SMichael Grosse        }
17980d784e1SMichael Grosse
180*24ab1f72SAndreas Gohr        $style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['b'] . ';}';
181*24ab1f72SAndreas Gohr
182*24ab1f72SAndreas Gohr        if($params['bh']) {
183*24ab1f72SAndreas Gohr            $style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $params['bh'] . ';}';
184*24ab1f72SAndreas Gohr        }
185*24ab1f72SAndreas Gohr
186*24ab1f72SAndreas Gohr        if($params['s'] || $params['f']) {
18780d784e1SMichael Grosse            $style .= 'g ' . $element . '{';
188*24ab1f72SAndreas Gohr            if($params['s']) $style .= 'stroke:' . $params['s'] . ';';
189*24ab1f72SAndreas Gohr            if($params['f']) $style .= 'fill:' . $params['f'] . ';';
1901072ee52SAndreas Gohr            $style .= '}';
1911072ee52SAndreas Gohr        }
1921072ee52SAndreas Gohr
193*24ab1f72SAndreas Gohr        if($params['sh'] || $params['fh']) {
19480d784e1SMichael Grosse            $style .= 'g:hover ' . $element . '{';
195*24ab1f72SAndreas Gohr            if($params['sh']) $style .= 'stroke:' . $params['sh'] . ';';
196*24ab1f72SAndreas Gohr            if($params['fh']) $style .= 'fill:' . $params['fh'] . ';';
1971072ee52SAndreas Gohr            $style .= '}';
1981072ee52SAndreas Gohr        }
1991072ee52SAndreas Gohr
2001072ee52SAndreas Gohr        return $style;
2011072ee52SAndreas Gohr    }
2021072ee52SAndreas Gohr
2031072ee52SAndreas Gohr    /**
2041072ee52SAndreas Gohr     * Takes a hexadecimal color string in the following forms:
2051072ee52SAndreas Gohr     *
2061072ee52SAndreas Gohr     * RGB
2071072ee52SAndreas Gohr     * RRGGBB
2081072ee52SAndreas Gohr     * RRGGBBAA
2091072ee52SAndreas Gohr     *
2101072ee52SAndreas Gohr     * Converts it to rgba() form
2111072ee52SAndreas Gohr     *
2121072ee52SAndreas Gohr     * @param string $color
2131072ee52SAndreas Gohr     * @return string
2141072ee52SAndreas Gohr     */
2151072ee52SAndreas Gohr    protected function fixColor($color) {
2161072ee52SAndreas Gohr        if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) {
2171072ee52SAndreas Gohr            $r = hexdec($m[1] . $m[1]);
2181072ee52SAndreas Gohr            $g = hexdec($m[2] . $m[2]);
2191072ee52SAndreas Gohr            $b = hexdec($m[3] . $m[3]);
2201072ee52SAndreas Gohr            $a = hexdec('ff');
2211072ee52SAndreas Gohr        } elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) {
2221072ee52SAndreas Gohr            $r = hexdec($m[1]);
2231072ee52SAndreas Gohr            $g = hexdec($m[2]);
2241072ee52SAndreas Gohr            $b = hexdec($m[3]);
2251072ee52SAndreas Gohr            if(isset($m[4])) {
2261072ee52SAndreas Gohr                $a = hexdec($m[4]);
2271072ee52SAndreas Gohr            } else {
2281072ee52SAndreas Gohr                $a = hexdec('ff');
2291072ee52SAndreas Gohr            }
2301072ee52SAndreas Gohr        } else {
2311072ee52SAndreas Gohr            return '';
2321072ee52SAndreas Gohr        }
2331072ee52SAndreas Gohr
2341072ee52SAndreas Gohr        return "rgba($r,$g,$b,$a)";
2351072ee52SAndreas Gohr    }
2361072ee52SAndreas Gohr
2371072ee52SAndreas Gohr    /**
23880d784e1SMichael Grosse     * sets a rectangular background of the size of the svg/this itself
23980d784e1SMichael Grosse     *
24080d784e1SMichael Grosse     * @param SvgNode $g
24180d784e1SMichael Grosse     * @return SvgNode
24280d784e1SMichael Grosse     */
243*24ab1f72SAndreas Gohr    protected function createBackground(SvgNode $g) {
24480d784e1SMichael Grosse        $rect = $g->prependChild('rect');
24580d784e1SMichael Grosse        $rect->addAttribute('class', self::BACKGROUNDCLASS);
24680d784e1SMichael Grosse
24780d784e1SMichael Grosse        $rect->addAttribute('x', '0');
24880d784e1SMichael Grosse        $rect->addAttribute('y', '0');
249*24ab1f72SAndreas Gohr        $rect->addAttribute('height', '100%');
250*24ab1f72SAndreas Gohr        $rect->addAttribute('width', '100%');
25180d784e1SMichael Grosse        return $rect;
25280d784e1SMichael Grosse    }
25380d784e1SMichael Grosse
25480d784e1SMichael Grosse    /**
2551072ee52SAndreas Gohr     * Abort processing with given status code
2561072ee52SAndreas Gohr     *
2571072ee52SAndreas Gohr     * @param int $status
2581072ee52SAndreas Gohr     */
2591072ee52SAndreas Gohr    protected function abort($status) {
2601072ee52SAndreas Gohr        http_status($status);
2611072ee52SAndreas Gohr        exit;
2621072ee52SAndreas Gohr    }
2631072ee52SAndreas Gohr
2641072ee52SAndreas Gohr}
2651072ee52SAndreas Gohr
2661072ee52SAndreas Gohr// main
2671072ee52SAndreas Gohr$svg = new SVG();
2681072ee52SAndreas Gohr$svg->out();
2691072ee52SAndreas Gohr
270