xref: /plugin/farmer/vendor/splitbrain/php-ringicon/src/RingIconSVG.php (revision c609f1dcc91a56df760d51ba92f6e25b7289002c)
1<?php
2
3namespace splitbrain\RingIcon;
4
5
6class RingIconSVG extends AbstractRingIcon
7{
8    public function __construct($size, $rings = 3)
9    {
10        parent::__construct($size, $rings);
11        // we don't downscale
12        $this->center = floor($this->size / 2);
13        $this->ringwidth = floor($this->size / $rings);
14    }
15
16    /**
17     * Generates an ring image svg suitable for inlining in html
18     *
19     * If a seed is given, the image will be based on that seed
20     *
21     * @param string $seed
22     *
23     * @return string
24     */
25    public function getInlineSVG($seed = '')
26    {
27        return $this->generateSVGImage($seed);
28    }
29
30    /**
31     * Generates an ring image svg
32     *
33     * If a seed is given, the image will be based on that seed
34     *
35     * @param string $seed initialize the genrator with this string
36     * @param string $file if given, the image is saved at that path, otherwise is printed as file to browser
37     */
38    public function createImage($seed = '', $file = '')
39    {
40        $svg = $this->generateSVGImage($seed, true);
41        if ($file) {
42            file_put_contents($file, $svg);
43        } else {
44            header("Content-type: image/svg+xml");
45            echo $svg;
46        }
47    }
48
49    /**
50     * Generates an ring image svg
51     *
52     * If a seed is given, the image will be based on that seed
53     *
54     * @param string $seed initialize the genrator with this string
55     * @param bool $file if true, the svg will have the markup suitable for being delivered as file
56     *
57     * @return string
58     */
59    protected function generateSVGImage($seed = '', $file = false)
60    {
61        if (!$seed) {
62            $seed = mt_rand() . time();
63        }
64        $this->seed = $seed;
65
66        // monochrome wanted?
67        if ($this->ismono) {
68            $this->generateMonoColor();
69        }
70
71        if ($file) {
72            $svgFileAttributes = array(
73                'xmlns' => 'http://www.w3.org/2000/svg',
74                'version' => '1.1',
75                'width' => $this->size,
76                'height' => $this->size,
77            );
78            $svgFileAttributes = implode(' ', array_map(
79                function ($k, $v) {
80                    return $k . '="' . htmlspecialchars($v) . '"';
81                },
82                array_keys($svgFileAttributes), $svgFileAttributes
83            ));
84        } else {
85            $svgFileAttributes = '';
86        }
87
88        $svg = "<svg $svgFileAttributes viewBox=\"0 0 $this->size $this->size\">";
89        $arcOuterRadious = $this->size / 2;
90        for ($i = $this->rings; $i > 0; $i--) {
91            $svg .= $this->createSVGArcPath($arcOuterRadious);
92            $arcOuterRadious -= $this->ringwidth / 2;
93        }
94        $svg .= '</svg>';
95
96        if ($file) {
97            $svgBoilerplate = '<?xml version="1.0" encoding="UTF-8"?>
98<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">';
99            $svg = $svgBoilerplate . $svg;
100        }
101
102        return $svg;
103    }
104
105    /**
106     * Draw a single arc
107     *
108     * @param float $outerRadius
109     *
110     * @return string
111     */
112    protected function createSVGArcPath($outerRadius)
113    {
114        $color = $this->randomCssColor();
115        $ringThickness = $this->ringwidth / 2;
116        $startAngle = $this->rand(20, 360);
117        $stopAngle = $this->rand(20, 360);
118        if ($stopAngle < $startAngle) {
119            list($startAngle, $stopAngle) = array($stopAngle, $startAngle);
120        }
121
122        list ($xStart, $yStart) = $this->polarToCartesian($outerRadius, $startAngle);
123        list ($xOuterEnd, $yOuterEnd) = $this->polarToCartesian($outerRadius, $stopAngle);
124
125        $innerRadius = $outerRadius - $ringThickness;
126        $SweepFlag = 0;
127        $innerSweepFlag = 1;
128        $largeArcFlag = (int)($stopAngle - $startAngle < 180);
129        list ($xInnerStart, $yInnerStart) = $this->polarToCartesian($outerRadius - $ringThickness, $stopAngle);
130        list ($xInnerEnd, $yInnerEnd) = $this->polarToCartesian($outerRadius - $ringThickness, $startAngle);
131
132        $fullPath = "<path fill='$color' d='
133            M $xStart $yStart
134            A $outerRadius $outerRadius 0 $largeArcFlag $SweepFlag $xOuterEnd $yOuterEnd
135            L $xInnerStart $yInnerStart
136            A $innerRadius $innerRadius 0 $largeArcFlag $innerSweepFlag $xInnerEnd $yInnerEnd
137            Z
138            '/>";
139        return $fullPath;
140    }
141
142    /**
143     * Create a random valid css color value
144     *
145     * @return string
146     */
147    protected function randomCssColor()
148    {
149        if ($this->ismono) {
150            $alpha = 1 - $this->rand(0, 96) / 100;
151            return "rgba({$this->monocolor[0]}, {$this->monocolor[1]}, {$this->monocolor[2]}, $alpha)";
152        }
153        $r = $this->rand(0, 255);
154        $g = $this->rand(0, 255);
155        $b = $this->rand(0, 255);
156        return "rgb($r, $g, $b)";
157    }
158
159    /**
160     * Calculate the x,y coordinate of a given angle at a given distance from the center
161     *
162     * 0 Degree is 3 o'clock
163     *
164     * @param float $radius
165     * @param float $angleInDegrees
166     *
167     * @return array
168     */
169    protected function polarToCartesian($radius, $angleInDegrees)
170    {
171        $angleInRadians = $angleInDegrees * M_PI / 180.0;
172
173        return array(
174            $this->size / 2 + ($radius * cos($angleInRadians)),
175            $this->size / 2 + ($radius * sin($angleInRadians)),
176        );
177    }
178
179}
180