xref: /template/sprintdoc/svg.php (revision 80d784e1de81a9bd4f2967a7d4fc39110dcb54d3)
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    }
27*80d784e1SMichael Grosse
28*80d784e1SMichael Grosse    /**
29*80d784e1SMichael Grosse     * @param \SimpleXMLElement $node the node to be added
30*80d784e1SMichael Grosse     * @return \SimpleXMLElement
31*80d784e1SMichael Grosse     */
32*80d784e1SMichael Grosse    public function appendNode(\SimpleXMLElement $node) {
33*80d784e1SMichael Grosse        $dom = dom_import_simplexml($this);
34*80d784e1SMichael Grosse        $domNode = dom_import_simplexml($node);
35*80d784e1SMichael Grosse
36*80d784e1SMichael Grosse        $newNode = $dom->appendChild($domNode);
37*80d784e1SMichael Grosse        return simplexml_import_dom($newNode, get_class($this));
38*80d784e1SMichael Grosse    }
39*80d784e1SMichael Grosse
40*80d784e1SMichael Grosse    /**
41*80d784e1SMichael Grosse     * @param \SimpleXMLElement $node the child to remove
42*80d784e1SMichael Grosse     * @return \SimpleXMLElement
43*80d784e1SMichael Grosse     */
44*80d784e1SMichael Grosse    public function removeChild(\SimpleXMLElement $node) {
45*80d784e1SMichael Grosse        $dom = dom_import_simplexml($node);
46*80d784e1SMichael Grosse        $dom->parentNode->removeChild($dom);
47*80d784e1SMichael Grosse        return $node;
48*80d784e1SMichael Grosse    }
491072ee52SAndreas Gohr}
501072ee52SAndreas Gohr
511072ee52SAndreas Gohr/**
521072ee52SAndreas Gohr * Manage SVG recoloring
531072ee52SAndreas Gohr */
541072ee52SAndreas Gohrclass SVG {
551072ee52SAndreas Gohr
561072ee52SAndreas Gohr    const IMGDIR = __DIR__ . '/img/';
57*80d784e1SMichael Grosse    const BACKGROUNDCLASS = 'sprintdoc-background';
581072ee52SAndreas Gohr
591072ee52SAndreas Gohr    /** @var SvgNode */
601072ee52SAndreas Gohr    protected $xml;
611072ee52SAndreas Gohr
621072ee52SAndreas Gohr    /**
631072ee52SAndreas Gohr     * SVG constructor
641072ee52SAndreas Gohr     */
651072ee52SAndreas Gohr    public function __construct() {
661072ee52SAndreas Gohr        global $INPUT;
671072ee52SAndreas Gohr
681072ee52SAndreas Gohr        $svg = cleanID($INPUT->str('svg'));
691072ee52SAndreas Gohr        if(blank($svg)) $this->abort(404);
701072ee52SAndreas Gohr
711072ee52SAndreas Gohr        // try local file first
721072ee52SAndreas Gohr        $file = self::IMGDIR . $svg;
731072ee52SAndreas Gohr        if(!file_exists($file)) {
741072ee52SAndreas Gohr            // media files are ACL protected
751072ee52SAndreas Gohr            if(auth_quickaclcheck($svg)) $this->abort(403);
761072ee52SAndreas Gohr            $file = mediaFN($svg);
771072ee52SAndreas Gohr        }
781072ee52SAndreas Gohr        // check if media exists
791072ee52SAndreas Gohr        if(!file_exists($file)) $this->abort(404);
801072ee52SAndreas Gohr
811072ee52SAndreas Gohr        $this->xml = simplexml_load_file($file, SvgNode::class);
821072ee52SAndreas Gohr    }
831072ee52SAndreas Gohr
841072ee52SAndreas Gohr    /**
851072ee52SAndreas Gohr     * Generate and output
861072ee52SAndreas Gohr     */
871072ee52SAndreas Gohr    public function out() {
88*80d784e1SMichael Grosse        $g = $this->wrapChildren();
89*80d784e1SMichael Grosse        $this->setBackground($g);
901072ee52SAndreas Gohr        $this->setStyle();
911072ee52SAndreas Gohr        header('image/svg+xml');
921072ee52SAndreas Gohr        echo $this->xml->asXML();
931072ee52SAndreas Gohr    }
941072ee52SAndreas Gohr
951072ee52SAndreas Gohr    /**
961072ee52SAndreas Gohr     * Generate a style setting from the input variables
971072ee52SAndreas Gohr     *
981072ee52SAndreas Gohr     * @return string
991072ee52SAndreas Gohr     */
1001072ee52SAndreas Gohr    protected function makeStyle() {
1011072ee52SAndreas Gohr        global $INPUT;
1021072ee52SAndreas Gohr
1031072ee52SAndreas Gohr        $element = 'path'; // FIXME configurable?
1041072ee52SAndreas Gohr
1051072ee52SAndreas Gohr        $colors = array(
1061072ee52SAndreas Gohr            's' => $this->fixColor($INPUT->str('s')),
1071072ee52SAndreas Gohr            'f' => $this->fixColor($INPUT->str('f')),
108*80d784e1SMichael Grosse            'b' => $this->fixColor($INPUT->str('b')),
1091072ee52SAndreas Gohr            'sh' => $this->fixColor($INPUT->str('sh')),
1101072ee52SAndreas Gohr            'fh' => $this->fixColor($INPUT->str('fh')),
111*80d784e1SMichael Grosse            'bh' => $this->fixColor($INPUT->str('bh')),
1121072ee52SAndreas Gohr        );
1131072ee52SAndreas Gohr
114*80d784e1SMichael Grosse        if (empty($colors['b'])) {
115*80d784e1SMichael Grosse            $colors['b'] = $this->fixColor('00000000');
116*80d784e1SMichael Grosse        }
117*80d784e1SMichael Grosse
118*80d784e1SMichael Grosse        $style = 'g rect.' . self::BACKGROUNDCLASS . '{fill:' . $colors['b'] . ';}';
119*80d784e1SMichael Grosse
120*80d784e1SMichael Grosse        if($colors['bh']) {
121*80d784e1SMichael Grosse            $style .= 'g:hover rect.' . self::BACKGROUNDCLASS . '{fill:' . $colors['bh'] . ';}';
122*80d784e1SMichael Grosse        }
123*80d784e1SMichael Grosse
1241072ee52SAndreas Gohr        if($colors['s'] || $colors['f']) {
125*80d784e1SMichael Grosse            $style .= 'g ' . $element . '{';
1261072ee52SAndreas Gohr            if($colors['s']) $style .= 'stroke:' . $colors['s'] . ';';
1271072ee52SAndreas Gohr            if($colors['f']) $style .= 'fill:' . $colors['f'] . ';';
1281072ee52SAndreas Gohr            $style .= '}';
1291072ee52SAndreas Gohr        }
1301072ee52SAndreas Gohr
1311072ee52SAndreas Gohr        if($colors['sh'] || $colors['fh']) {
132*80d784e1SMichael Grosse            $style .= 'g:hover ' . $element . '{';
1331072ee52SAndreas Gohr            if($colors['sh']) $style .= 'stroke:' . $colors['sh'] . ';';
1341072ee52SAndreas Gohr            if($colors['fh']) $style .= 'fill:' . $colors['fh'] . ';';
1351072ee52SAndreas Gohr            $style .= '}';
1361072ee52SAndreas Gohr        }
1371072ee52SAndreas Gohr
1381072ee52SAndreas Gohr        return $style;
1391072ee52SAndreas Gohr    }
1401072ee52SAndreas Gohr
1411072ee52SAndreas Gohr    /**
1421072ee52SAndreas Gohr     * Takes a hexadecimal color string in the following forms:
1431072ee52SAndreas Gohr     *
1441072ee52SAndreas Gohr     * RGB
1451072ee52SAndreas Gohr     * RRGGBB
1461072ee52SAndreas Gohr     * RRGGBBAA
1471072ee52SAndreas Gohr     *
1481072ee52SAndreas Gohr     * Converts it to rgba() form
1491072ee52SAndreas Gohr     *
1501072ee52SAndreas Gohr     * @param string $color
1511072ee52SAndreas Gohr     * @return string
1521072ee52SAndreas Gohr     */
1531072ee52SAndreas Gohr    protected function fixColor($color) {
1541072ee52SAndreas Gohr        if(preg_match('/^([0-9a-f])([0-9a-f])([0-9a-f])$/i', $color, $m)) {
1551072ee52SAndreas Gohr            $r = hexdec($m[1] . $m[1]);
1561072ee52SAndreas Gohr            $g = hexdec($m[2] . $m[2]);
1571072ee52SAndreas Gohr            $b = hexdec($m[3] . $m[3]);
1581072ee52SAndreas Gohr            $a = hexdec('ff');
1591072ee52SAndreas Gohr        } elseif(preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $color, $m)) {
1601072ee52SAndreas Gohr            $r = hexdec($m[1]);
1611072ee52SAndreas Gohr            $g = hexdec($m[2]);
1621072ee52SAndreas Gohr            $b = hexdec($m[3]);
1631072ee52SAndreas Gohr            if(isset($m[4])) {
1641072ee52SAndreas Gohr                $a = hexdec($m[4]);
1651072ee52SAndreas Gohr            } else {
1661072ee52SAndreas Gohr                $a = hexdec('ff');
1671072ee52SAndreas Gohr            }
1681072ee52SAndreas Gohr        } else {
1691072ee52SAndreas Gohr            return '';
1701072ee52SAndreas Gohr        }
1711072ee52SAndreas Gohr
1721072ee52SAndreas Gohr        return "rgba($r,$g,$b,$a)";
1731072ee52SAndreas Gohr    }
1741072ee52SAndreas Gohr
1751072ee52SAndreas Gohr    /**
176*80d784e1SMichael Grosse     * sets a rectangular background of the size of the svg/this itself
177*80d784e1SMichael Grosse     *
178*80d784e1SMichael Grosse     * @param SvgNode $g
179*80d784e1SMichael Grosse     * @return SvgNode
180*80d784e1SMichael Grosse     */
181*80d784e1SMichael Grosse    protected function setBackground(SvgNode $g) {
182*80d784e1SMichael Grosse        $attributes = $this->xml->attributes();
183*80d784e1SMichael Grosse        $rect = $g->prependChild('rect');
184*80d784e1SMichael Grosse        $rect->addAttribute('class', self::BACKGROUNDCLASS);
185*80d784e1SMichael Grosse
186*80d784e1SMichael Grosse        $rect->addAttribute('x', '0');
187*80d784e1SMichael Grosse        $rect->addAttribute('y', '0');
188*80d784e1SMichael Grosse        $rect->addAttribute('height', $attributes['height']);
189*80d784e1SMichael Grosse        $rect->addAttribute('width', $attributes['width']);
190*80d784e1SMichael Grosse        return $rect;
191*80d784e1SMichael Grosse    }
192*80d784e1SMichael Grosse
193*80d784e1SMichael Grosse    /**
194*80d784e1SMichael Grosse     * Wraps all elements of $this in a `<g>` tag
195*80d784e1SMichael Grosse     *
196*80d784e1SMichael Grosse     * @return SvgNode
197*80d784e1SMichael Grosse     */
198*80d784e1SMichael Grosse    protected function wrapChildren() {
199*80d784e1SMichael Grosse        $svgChildren = array();
200*80d784e1SMichael Grosse        foreach ($this->xml->children() as $child) {
201*80d784e1SMichael Grosse            $svgChildren[] = $this->xml->removeChild($child);
202*80d784e1SMichael Grosse        }
203*80d784e1SMichael Grosse        $g = $this->xml->prependChild('g');
204*80d784e1SMichael Grosse        foreach ($svgChildren as $child) {
205*80d784e1SMichael Grosse            $g->appendNode($child);
206*80d784e1SMichael Grosse        }
207*80d784e1SMichael Grosse        return $g;
208*80d784e1SMichael Grosse    }
209*80d784e1SMichael Grosse
210*80d784e1SMichael Grosse    /**
2111072ee52SAndreas Gohr     * Apply the style to the SVG
2121072ee52SAndreas Gohr     */
2131072ee52SAndreas Gohr    protected function setStyle() {
2141072ee52SAndreas Gohr        $defs = $this->xml->defs;
2151072ee52SAndreas Gohr        if(!$defs) {
2161072ee52SAndreas Gohr            $defs = $this->xml->prependChild('defs');
2171072ee52SAndreas Gohr        }
2181072ee52SAndreas Gohr        $defs->addChild('style', $this->makeStyle());
2191072ee52SAndreas Gohr    }
2201072ee52SAndreas Gohr
2211072ee52SAndreas Gohr    /**
2221072ee52SAndreas Gohr     * Abort processing with given status code
2231072ee52SAndreas Gohr     *
2241072ee52SAndreas Gohr     * @param int $status
2251072ee52SAndreas Gohr     */
2261072ee52SAndreas Gohr    protected function abort($status) {
2271072ee52SAndreas Gohr        http_status($status);
2281072ee52SAndreas Gohr        exit;
2291072ee52SAndreas Gohr    }
2301072ee52SAndreas Gohr
2311072ee52SAndreas Gohr}
2321072ee52SAndreas Gohr
2331072ee52SAndreas Gohr// main
2341072ee52SAndreas Gohr$svg = new SVG();
2351072ee52SAndreas Gohr$svg->out();
2361072ee52SAndreas Gohr
237