|
@@ -10,9 +10,10 @@
|
|
|
|
|
|
|
|
namespace chillerlan\QRCode\Output;
|
|
namespace chillerlan\QRCode\Output;
|
|
|
|
|
|
|
|
|
|
+use chillerlan\QRCode\Data\QRMatrix;
|
|
|
use chillerlan\QRCode\QRCode;
|
|
use chillerlan\QRCode\QRCode;
|
|
|
|
|
|
|
|
-use function is_string, sprintf, strip_tags, trim;
|
|
|
|
|
|
|
+use function implode, is_string, ksort, sprintf, strip_tags, trim;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Converts the matrix into markup types: HTML, SVG, ...
|
|
* Converts the matrix into markup types: HTML, SVG, ...
|
|
@@ -21,12 +22,6 @@ class QRMarkup extends QROutputAbstract{
|
|
|
|
|
|
|
|
protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
|
|
protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * @see \sprintf()
|
|
|
|
|
- */
|
|
|
|
|
- protected string $svgHeader = '<svg xmlns="http://www.w3.org/2000/svg"'.
|
|
|
|
|
- ' class="qr-svg %1$s" style="width: 100%%; height: auto;" viewBox="0 0 %2$d %2$d">';
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* @inheritDoc
|
|
* @inheritDoc
|
|
|
*/
|
|
*/
|
|
@@ -41,7 +36,7 @@ class QRMarkup extends QROutputAbstract{
|
|
|
: $this->options->markupLight;
|
|
: $this->options->markupLight;
|
|
|
}
|
|
}
|
|
|
else{
|
|
else{
|
|
|
- $this->moduleValues[$M_TYPE] = trim(strip_tags($v), '\'"');
|
|
|
|
|
|
|
+ $this->moduleValues[$M_TYPE] = trim(strip_tags($v), " '\"\r\n\t");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|
|
@@ -84,75 +79,129 @@ class QRMarkup extends QROutputAbstract{
|
|
|
* SVG output
|
|
* SVG output
|
|
|
*
|
|
*
|
|
|
* @see https://github.com/codemasher/php-qrcode/pull/5
|
|
* @see https://github.com/codemasher/php-qrcode/pull/5
|
|
|
|
|
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
|
|
|
|
|
+ * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
|
|
|
*/
|
|
*/
|
|
|
protected function svg(string $file = null):string{
|
|
protected function svg(string $file = null):string{
|
|
|
- $matrix = $this->matrix->matrix();
|
|
|
|
|
|
|
+ $svg = $this->svgHeader();
|
|
|
|
|
|
|
|
- $svg = sprintf($this->svgHeader, $this->options->cssClass, $this->options->svgViewBoxSize ?? $this->moduleCount)
|
|
|
|
|
- .$this->options->eol
|
|
|
|
|
- .'<defs>'.$this->options->svgDefs.'</defs>'
|
|
|
|
|
- .$this->options->eol;
|
|
|
|
|
|
|
+ if(!empty($this->options->svgDefs)){
|
|
|
|
|
+ $svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- foreach($this->moduleValues as $M_TYPE => $value){
|
|
|
|
|
- $path = '';
|
|
|
|
|
|
|
+ $svg .= $this->svgPaths();
|
|
|
|
|
|
|
|
- foreach($matrix as $y => $row){
|
|
|
|
|
- //we'll combine active blocks within a single row as a lightweight compression technique
|
|
|
|
|
- $start = null;
|
|
|
|
|
- $count = 0;
|
|
|
|
|
|
|
+ // close svg
|
|
|
|
|
+ $svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
|
|
|
|
|
|
|
|
- foreach($row as $x => $module){
|
|
|
|
|
|
|
+ // transform to data URI only when not saving to file
|
|
|
|
|
+ if($file === null && $this->options->imageBase64){
|
|
|
|
|
+ $svg = $this->base64encode($svg, 'image/svg+xml');
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if($module === $M_TYPE){
|
|
|
|
|
- $count++;
|
|
|
|
|
|
|
+ return $svg;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if($start === null){
|
|
|
|
|
- $start = $x;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * returns the <svg> header with the given options parsed
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function svgHeader():string{
|
|
|
|
|
+ $width = $this->options->svgWidth !== null ? sprintf(' width="%s"', $this->options->svgWidth) : '';
|
|
|
|
|
+ $height = $this->options->svgHeight !== null ? sprintf(' height="%s"', $this->options->svgHeight) : '';
|
|
|
|
|
+
|
|
|
|
|
+ /** @noinspection HtmlUnknownAttribute */
|
|
|
|
|
+ return sprintf(
|
|
|
|
|
+ '<?xml version="1.0" encoding="UTF-8"?>%6$s'.
|
|
|
|
|
+ '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" viewBox="0 0 %2$s %2$s" preserveAspectRatio="%3$s"%4$s%5$s>%6$s',
|
|
|
|
|
+ $this->options->cssClass,
|
|
|
|
|
+ $this->options->svgViewBoxSize ?? $this->moduleCount,
|
|
|
|
|
+ $this->options->svgPreserveAspectRatio,
|
|
|
|
|
+ $width,
|
|
|
|
|
+ $height,
|
|
|
|
|
+ $this->options->eol
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if(isset($row[$x + 1])){
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * returns one or more SVG <path> elements
|
|
|
|
|
+ *
|
|
|
|
|
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function svgPaths():string{
|
|
|
|
|
+ $paths = [];
|
|
|
|
|
|
|
|
- if($count > 0){
|
|
|
|
|
- $len = $count;
|
|
|
|
|
- $start ??= 0; // avoid type coercion in sprintf() - phan happy
|
|
|
|
|
|
|
+ // collect the modules for each type
|
|
|
|
|
+ foreach($this->matrix->matrix() as $y => $row){
|
|
|
|
|
+ foreach($row as $x => $M_TYPE){
|
|
|
|
|
|
|
|
- $path .= sprintf('M%s %s h%s v1 h-%sZ ', $start, $y, $len, $len);
|
|
|
|
|
|
|
+ if($this->options->svgConnectPaths && !$this->matrix->checkTypes($x, $y, $this->options->svgExcludeFromConnect)){
|
|
|
|
|
+ // to connect paths we'll redeclare the $M_TYPE to data only
|
|
|
|
|
+ $M_TYPE = QRMatrix::M_DATA;
|
|
|
|
|
|
|
|
- // reset count
|
|
|
|
|
- $count = 0;
|
|
|
|
|
- $start = null;
|
|
|
|
|
|
|
+ if($this->matrix->check($x, $y)){
|
|
|
|
|
+ $M_TYPE |= QRMatrix::IS_DARK;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // collect the modules per $M_TYPE
|
|
|
|
|
+ $paths[$M_TYPE][] = $this->svgModule($x, $y);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // beautify output
|
|
|
|
|
+ ksort($paths);
|
|
|
|
|
+
|
|
|
|
|
+ $svg = [];
|
|
|
|
|
|
|
|
- if(!empty($path)){
|
|
|
|
|
- $svg .= sprintf(
|
|
|
|
|
- '<path class="qr-%s %s" stroke="transparent" fill="%s" fill-opacity="%s" d="%s" />',
|
|
|
|
|
- $M_TYPE, $this->options->cssClass, $value, $this->options->svgOpacity, $path
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ // create the path elements
|
|
|
|
|
+ foreach($paths as $M_TYPE => $path){
|
|
|
|
|
+ $path = trim(implode(' ', $path));
|
|
|
|
|
+
|
|
|
|
|
+ if(empty($path)){
|
|
|
|
|
+ continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ $cssClass = implode(' ', [
|
|
|
|
|
+ 'qr-'.$M_TYPE,
|
|
|
|
|
+ ($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK ? 'dark' : 'light',
|
|
|
|
|
+ $this->options->cssClass,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $format = empty($this->moduleValues[$M_TYPE])
|
|
|
|
|
+ ? '<path class="%1$s" d="%2$s"/>'
|
|
|
|
|
+ : '<path class="%1$s" fill="%3$s" fill-opacity="%4$s" d="%2$s"/>';
|
|
|
|
|
+
|
|
|
|
|
+ $svg[] = sprintf($format, $cssClass, $path, $this->moduleValues[$M_TYPE], $this->options->svgOpacity);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // close svg
|
|
|
|
|
- $svg .= '</svg>'.$this->options->eol;
|
|
|
|
|
|
|
+ return implode($this->options->eol, $svg);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // if saving to file, append the correct headers
|
|
|
|
|
- if($file !== null){
|
|
|
|
|
- return '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">'.
|
|
|
|
|
- $this->options->eol.$svg;
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * returns a path segment for a single module
|
|
|
|
|
+ *
|
|
|
|
|
+ * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function svgModule(int $x, int $y):string{
|
|
|
|
|
+
|
|
|
|
|
+ if($this->options->imageTransparent && !$this->matrix->check($x, $y)){
|
|
|
|
|
+ return '';
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if($this->options->imageBase64){
|
|
|
|
|
- $svg = $this->base64encode($svg, 'image/svg+xml');
|
|
|
|
|
|
|
+ if($this->options->svgDrawCircularModules && !$this->matrix->checkTypes($x, $y, $this->options->svgKeepAsSquare)){
|
|
|
|
|
+ $r = $this->options->svgCircleRadius;
|
|
|
|
|
+
|
|
|
|
|
+ return sprintf(
|
|
|
|
|
+ 'M%1$s %2$s a%3$s %3$s 0 1 0 %4$s 0 a%3$s,%3$s 0 1 0 -%4$s 0Z',
|
|
|
|
|
+ ($x + 0.5 - $r),
|
|
|
|
|
+ ($y + 0.5),
|
|
|
|
|
+ $r,
|
|
|
|
|
+ ($r * 2)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return $svg;
|
|
|
|
|
|
|
+ return sprintf('M%1$s %2$s h%3$s v1 h-%4$sZ', $x, $y, 1, 1);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
}
|
|
}
|