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