1<?php 2 3namespace Mpdf\QrCode\Output; 4 5use Mpdf\QrCode\QrCode; 6use SimpleXMLElement; 7 8class Svg 9{ 10 11 /** 12 * @param QrCode $qrCode QR code instance 13 * @param int $size The width / height of the resulting SVG 14 * @param string $background The background color, e. g. "white", "rgb(0,0,0)" or "cmyk(0,0,0,0)" 15 * @param string $color The foreground and border color, e. g. "black", "rgb(255,255,255)" or "cmyk(0,0,0,100)" 16 * 17 * @return string Binary image data 18 */ 19 public function output(QrCode $qrCode, $size = 100, $background = 'white', $color = 'black') 20 { 21 $qrSize = $qrCode->getQrSize(); 22 $final = $qrCode->getFinal(); 23 24 if ($qrCode->isBorderDisabled()) { 25 $minSize = 4; 26 $maxSize = $qrSize - 4; 27 } else { 28 $minSize = 0; 29 $maxSize = $qrSize; 30 } 31 32 $rectSize = $size / ($maxSize - $minSize); 33 34 $svg = new SimpleXMLElement('<svg></svg>'); 35 $svg->addAttribute('version', '1.1'); 36 $svg->addAttribute('xmlns', 'http://www.w3.org/2000/svg'); 37 $svg->addAttribute('width', $size); 38 $svg->addAttribute('height', $size); 39 40 $this->addChild( 41 $svg, 42 'rect', 43 [ 44 'x' => 0, 45 'y' => 0, 46 'width' => $size, 47 'height' => $size, 48 'fill' => $background, 49 ] 50 ); 51 52 for ($row = $minSize; $row < $maxSize; $row++) { 53 // Simple compression: pixels in a row will be compressed into the same rectangle. 54 $startX = null; 55 $y = ($row - $minSize) * $rectSize; 56 for ($column = $minSize; $column < $maxSize; $column++) { 57 $x = ($column - $minSize) * $rectSize; 58 if ($final[$column + $row * $qrSize + 1]) { 59 if ($startX === null) { 60 $startX = $x; 61 } 62 } elseif ($startX !== null) { 63 $this->addChild( 64 $svg, 65 'rect', 66 [ 67 'x' => $startX, 68 'y' => $y, 69 'width' => $x - $startX, 70 'height' => $rectSize, 71 'fill' => $color, 72 ] 73 ); 74 $startX = null; 75 } 76 } 77 78 if ($startX !== null) { 79 $x = ($column - $minSize) * $rectSize; 80 $this->addChild( 81 $svg, 82 'rect', 83 [ 84 'x' => $startX, 85 'y' => $y, 86 'width' => $x - $startX, 87 'height' => $rectSize, 88 'fill' => $color, 89 ] 90 ); 91 } 92 } 93 94 for ($column = $minSize; $column < $maxSize; $column++) { 95 // Simple compression: pixels in a column will be compressed into the same rectangle. 96 $startY = null; 97 $x = ($column - $minSize) * $rectSize; 98 for ($row = $minSize; $row < $maxSize; $row++) { 99 $y = ($row - $minSize) * $rectSize; 100 if ($final[$column + $row * $qrSize + 1]) { 101 if ($startY === null) { 102 $startY = $y; 103 } 104 } elseif ($startY !== null) { 105 if ($startY < $y - $rectSize) { 106 // Only drawn 2+ columns 107 $this->addChild( 108 $svg, 109 'rect', 110 [ 111 'x' => $x, 112 'y' => $startY, 113 'width' => $rectSize, 114 'height' => $y - $startY, 115 'fill' => $color, 116 ] 117 ); 118 } 119 $startY = null; 120 } 121 } 122 123 if ($startY !== null) { 124 $y = ($row - $minSize) * $rectSize; 125 $this->addChild( 126 $svg, 127 'rect', 128 [ 129 'x' => $x, 130 'y' => $startY, 131 'width' => $rectSize, 132 'height' => $y - $startY, 133 'fill' => $color, 134 ] 135 ); 136 } 137 } 138 139 return $svg->asXML(); 140 } 141 142 143 /** 144 * Adds a child with the given attributes 145 * 146 * @param SimpleXMLElement $svg 147 * @param string $name 148 * @param array $attributes 149 * 150 * @return SimpleXMLElement 151 */ 152 public function addChild(SimpleXMLElement $svg, $name, array $attributes = []) 153 { 154 $child = $svg->addChild($name); 155 156 foreach ($attributes as $key => $value) { 157 $child->addAttribute((string) $key, (string) $value); 158 } 159 160 return $child; 161 } 162} 163