Jelajahi Sumber

:sparkles: better SVG output

smiley 8 tahun lalu
induk
melakukan
465351ca86
3 mengubah file dengan 81 tambahan dan 42 penghapusan
  1. 30 18
      examples/svg.php
  2. 33 24
      src/Output/QRMarkup.php
  3. 18 0
      src/QROptions.php

+ 30 - 18
examples/svg.php

@@ -17,39 +17,51 @@ require_once __DIR__.'/../vendor/autoload.php';
 $data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
 $data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
 
 
 $options = new QROptions([
 $options = new QROptions([
-	'version'      => 5,
+	'version'      => 20,
 	'outputType'   => QRCode::OUTPUT_MARKUP_SVG,
 	'outputType'   => QRCode::OUTPUT_MARKUP_SVG,
 	'eccLevel'     => QRCode::ECC_L,
 	'eccLevel'     => QRCode::ECC_L,
-	'scale'        => 10,
+	'scale'        => 5,
+	'addQuietzone' => true,
+	'svgOpacity' => 0.8,
+	'svgDefs' => '
+		<linearGradient id="g2">
+			<stop offset="0%" stop-color="#39F" />
+			<stop offset="100%" stop-color="#F3F" />
+		</linearGradient>
+		<linearGradient id="g1">
+			<stop offset="0%" stop-color="#F3F" />
+			<stop offset="100%" stop-color="#39F" />
+		</linearGradient>
+		<style>rect{shape-rendering:crispEdges}</style>',
 	'moduleValues' => [
 	'moduleValues' => [
 		// finder
 		// finder
-		1536 => '#A71111', // dark (true)
-		6    => '#FFBFBF', // light (false)
+		1536 => 'url(#g1)', // dark (true)
+		6    => '#eee', // light (false)
 		// alignment
 		// alignment
-		2560 => '#A70364',
-		10   => '#FFC9C9',
+		2560 => 'url(#g1)',
+		10   => '#eee',
 		// timing
 		// timing
-		3072 => '#98005D',
-		12   => '#FFB8E9',
+		3072 => 'url(#g1)',
+		12   => '#eee',
 		// format
 		// format
-		3584 => '#003804',
-		14   => '#00FB12',
+		3584 => 'url(#g1)',
+		14   => '#eee',
 		// version
 		// version
-		4096 => '#650098',
-		16   => '#E0B8FF',
+		4096 => 'url(#g1)',
+		16   => '#eee',
 		// data
 		// data
-		1024 => '#4A6000',
-		4    => '#ECF9BE',
+		1024 => 'url(#g2)',
+		4    => '#eee',
 		// darkmodule
 		// darkmodule
-		512  => '#080063',
+		512  => 'url(#g1)',
 		// separator
 		// separator
-		8    => '#AFBFBF',
+		8    => '#eee',
 		// quietzone
 		// quietzone
-		18   => '#FFFFFF',
+		18   => '#eee',
 	],
 	],
 ]);
 ]);
 
 
-header('Content-type: image/svg+xml');
+#header('Content-type: image/svg+xml');
 
 
 echo (new QRCode($options))->render($data);
 echo (new QRCode($options))->render($data);
 
 

+ 33 - 24
src/Output/QRMarkup.php

@@ -21,6 +21,7 @@ class QRMarkup extends QROutputAbstract{
 
 
 	/**
 	/**
 	 * @return string
 	 * @return string
+	 * @throws \chillerlan\QRCode\Output\QRCodeOutputException
 	 */
 	 */
 	public function dump(){
 	public function dump(){
 
 
@@ -68,52 +69,60 @@ class QRMarkup extends QROutputAbstract{
 	 * @return string|bool
 	 * @return string|bool
 	 */
 	 */
 	protected function toSVG(){
 	protected function toSVG(){
-		$length = ($this->moduleCount + ($this->options->addQuietzone ? 8 : 0)) * $this->options->scale;
+		$scale  = $this->options->scale;
+		$length = $this->moduleCount * $scale;
+		$matrix = $this->matrix->matrix();
 
 
-		// svg header
-		$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$length.'" height="'.$length.'" viewBox="0 0 '.$length.' '.$length.'">'.$this->options->eol.
-		       '<defs><style>rect{shape-rendering:crispEdges}</style></defs>'.$this->options->eol;
+		$svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="'.$length.'px" height="'.$length.'px">'
+		       .$this->options->eol
+		       .'<defs>'.$this->options->svgDefs.'</defs>'
+		       .$this->options->eol;
 
 
-		// @todo: optimize -> see https://github.com/alexeyten/qr-image/blob/master/lib/vector.js
-		foreach($this->options->moduleValues as $key => $value){
+		foreach($this->options->moduleValues as $M_TYPE => $value){
 
 
 			// fallback
 			// fallback
 			if(is_bool($value)){
 			if(is_bool($value)){
 				$value = $value ? '#000' : '#fff';
 				$value = $value ? '#000' : '#fff';
 			}
 			}
 
 
-			// svg body
-			foreach($this->matrix->matrix() as $y => $row){
+			$path = '';
+
+			foreach($matrix as $y => $row){
 				//we'll combine active blocks within a single row as a lightweight compression technique
 				//we'll combine active blocks within a single row as a lightweight compression technique
-				$from  = -1;
+				$start = null;
 				$count = 0;
 				$count = 0;
 
 
-				foreach($row as $x => $pixel){
-					if($pixel === $key){
+				foreach($row as $x => $module){
+
+					if($module === $M_TYPE){
 						$count++;
 						$count++;
 
 
-						if($from < 0){
-							$from = $x;
+						if($start === null){
+							$start = $x * $scale;
+						}
+
+						if($row[$x + 1] ?? false){
+							continue;
 						}
 						}
 					}
 					}
-					elseif($from >= 0){
-						$svg .= '<rect x="'.($from * $this->options->scale).'" y="'.($y * $this->options->scale)
-						        .'" width="'.($this->options->scale * $count).'" height="'.$this->options->scale.'" fill="'.$value.'"'
-						        .(trim($this->options->cssClass) !== '' ? ' class="'.$this->options->cssClass.'"' :'').' />'
-						        .$this->options->eol;
+
+					if($count > 0){
+						$len = $count * $scale;
+						$path .= 'M' .$start. ' ' .($y * $scale). ' h'.$len.' v'.$scale.' h-'.$len.'Z ';
 
 
 						// reset count
 						// reset count
-						$from  = -1;
 						$count = 0;
 						$count = 0;
+						$start = null;
 					}
 					}
-				}
 
 
-				// close off the row, if applicable
-				if($from >= 0){
-					$svg .= '<rect x="'.($from * $this->options->scale).'" y="'.($y * $this->options->scale)
-					        .'" width="'.($this->options->scale * $count).'" height="'.$this->options->scale.'" class="'.$this->options->cssClass.'" fill="'.$value.'" />'.$this->options->eol;
 				}
 				}
+
 			}
 			}
+
+			if(!empty($path)){
+				$svg .= '<path class="qr-'.$M_TYPE.' '.$this->options->cssClass.'" stroke="transparent" fill="'.$value.'" fill-opacity="'.$this->options->svgOpacity.'" d="'.$path.'" />';
+			}
+
 		}
 		}
 
 
 		// close svg
 		// close svg

+ 18 - 0
src/QROptions.php

@@ -32,6 +32,8 @@ use chillerlan\Traits\Container;
  * @property int    $scale
  * @property int    $scale
  *
  *
  * @property string $cssClass
  * @property string $cssClass
+ * @property string $svgOpacity
+ * @property string $svgDefs
  *
  *
  * @property string $textDark
  * @property string $textDark
  * @property string $textLight
  * @property string $textLight
@@ -154,6 +156,22 @@ class QROptions{
 	 */
 	 */
 	protected $cssClass;
 	protected $cssClass;
 
 
+	/**
+	 * SVG opacity
+	 *
+	 * @var float
+	 */
+	protected $svgOpacity = 1.0;
+
+	/**
+	 * anything between <defs>
+	 *
+	 * @see https://developer.mozilla.org/docs/Web/SVG/Element/defs
+	 *
+	 * @var string
+	 */
+	protected $svgDefs = '<style>rect{shape-rendering:crispEdges}</style>';
+
 	/**
 	/**
 	 * string substitute for dark
 	 * string substitute for dark
 	 *
 	 *