mirror of
https://github.com/chillerlan/php-qrcode.git
synced 2026-09-01 13:08:14 +00:00
🛀 extract QRMarkup into QRMarkupSVG and QRMarkupHTML
This commit is contained in:
@@ -10,44 +10,26 @@
|
||||
|
||||
namespace chillerlan\QRCodeExamples;
|
||||
|
||||
use chillerlan\QRCode\Output\QRMarkup;
|
||||
use function file_get_contents, sprintf;
|
||||
use chillerlan\QRCode\Output\QRMarkupSVG;
|
||||
use function ceil, file_get_contents, sprintf;
|
||||
|
||||
/**
|
||||
* Create SVG QR Codes with embedded logos (that are also SVG)
|
||||
*/
|
||||
class QRSvgWithLogo extends QRMarkup{
|
||||
class QRSvgWithLogo extends QRMarkupSVG{
|
||||
|
||||
/**
|
||||
* SVG output
|
||||
*
|
||||
* @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/
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function svg(bool $saveToFile):string{
|
||||
protected function paths():string{
|
||||
$size = (int)ceil($this->moduleCount * $this->options->svgLogoScale);
|
||||
|
||||
// we're calling QRMatrix::setLogoSpace() manually, so QROptions::$addLogoSpace has no effect here
|
||||
$this->matrix->setLogoSpace($size, $size);
|
||||
|
||||
$svg = $this->svgHeader();
|
||||
|
||||
if(!empty($this->options->svgDefs)){
|
||||
$svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
|
||||
}
|
||||
|
||||
$svg .= $this->svgPaths();
|
||||
$svg = parent::paths();
|
||||
$svg .= $this->getLogo();
|
||||
|
||||
// close svg
|
||||
$svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
|
||||
|
||||
// transform to data URI only when not saving to file
|
||||
if(!$saveToFile && $this->options->imageBase64){
|
||||
$svg = $this->base64encode($svg, 'image/svg+xml');
|
||||
}
|
||||
|
||||
return $svg;
|
||||
}
|
||||
|
||||
|
||||
+6
-152
@@ -10,15 +10,12 @@
|
||||
|
||||
namespace chillerlan\QRCode\Output;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
|
||||
use function implode, is_string, sprintf, strip_tags, trim;
|
||||
use function is_string, strip_tags, trim;
|
||||
|
||||
/**
|
||||
* Converts the matrix into markup types: HTML, SVG, ...
|
||||
* Abstract for markup types: HTML, SVG, ... XML anyone?
|
||||
*/
|
||||
class QRMarkup extends QROutputAbstract{
|
||||
abstract class QRMarkup extends QROutputAbstract{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
@@ -44,18 +41,11 @@ class QRMarkup extends QROutputAbstract{
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dump(string $file = null){
|
||||
public function dump(string $file = null):string{
|
||||
$file ??= $this->options->cachefile;
|
||||
$saveToFile = $file !== null;
|
||||
|
||||
switch($this->options->outputType){
|
||||
case QRCode::OUTPUT_MARKUP_HTML:
|
||||
$data = $this->html($saveToFile);
|
||||
break;
|
||||
case QRCode::OUTPUT_MARKUP_SVG:
|
||||
default:
|
||||
$data = $this->svg($saveToFile);
|
||||
}
|
||||
$data = $this->createMarkup($saveToFile);
|
||||
|
||||
if($saveToFile){
|
||||
$this->saveToFile($data, $file);
|
||||
@@ -65,143 +55,7 @@ class QRMarkup extends QROutputAbstract{
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML output
|
||||
*/
|
||||
protected function html(bool $saveToFile):string{
|
||||
|
||||
$html = empty($this->options->cssClass)
|
||||
? '<div>'
|
||||
: sprintf('<div class="%s">', $this->options->cssClass);
|
||||
|
||||
$html .= $this->options->eol;
|
||||
|
||||
foreach($this->matrix->matrix() as $row){
|
||||
$html .= '<div>';
|
||||
|
||||
foreach($row as $M_TYPE){
|
||||
$html .= sprintf('<span style="background: %s;"></span>', $this->moduleValues[$M_TYPE]);
|
||||
}
|
||||
|
||||
$html .= '</div>'.$this->options->eol;
|
||||
}
|
||||
|
||||
$html .= '</div>'.$this->options->eol;
|
||||
|
||||
if($saveToFile){
|
||||
return sprintf(
|
||||
'<!DOCTYPE html><head><meta charset="UTF-8"><title>QR Code</title></head><body>%s</body>',
|
||||
$this->options->eol.$html
|
||||
);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* SVG output
|
||||
*
|
||||
* @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(bool $saveToFile):string{
|
||||
$svg = $this->svgHeader();
|
||||
|
||||
if(!empty($this->options->svgDefs)){
|
||||
$svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
|
||||
}
|
||||
|
||||
$svg .= $this->svgPaths();
|
||||
|
||||
// close svg
|
||||
$svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
|
||||
|
||||
// transform to data URI only when not saving to file
|
||||
if(!$saveToFile && $this->options->imageBase64){
|
||||
$svg = $this->base64encode($svg, 'image/svg+xml');
|
||||
}
|
||||
|
||||
return $svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns one or more SVG <path> elements
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
|
||||
*/
|
||||
protected function svgPaths():string{
|
||||
$paths = $this->collectModules(fn(int $x, int $y):string => $this->svgModule($x, $y));
|
||||
$svg = [];
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return implode($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->drawCircularModules && $this->matrix->checkTypeNotIn($x, $y, $this->options->keepAsSquare)){
|
||||
$r = $this->options->circleRadius;
|
||||
|
||||
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 sprintf('M%1$s %2$s h%3$s v1 h-%4$sZ', $x, $y, 1, 1);
|
||||
}
|
||||
|
||||
abstract protected function createMarkup(bool $saveToFile):string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* Class QRMarkupHTML
|
||||
*
|
||||
* @created 06.06.2022
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2022 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Output;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* HTML output
|
||||
*/
|
||||
class QRMarkupHTML extends QRMarkup{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function createMarkup(bool $saveToFile):string{
|
||||
$html = empty($this->options->cssClass)
|
||||
? '<div>'
|
||||
: sprintf('<div class="%s">', $this->options->cssClass);
|
||||
|
||||
$html .= $this->options->eol;
|
||||
|
||||
foreach($this->matrix->matrix() as $row){
|
||||
$html .= '<div>';
|
||||
|
||||
foreach($row as $M_TYPE){
|
||||
$html .= sprintf('<span style="background: %s;"></span>', $this->moduleValues[$M_TYPE]);
|
||||
}
|
||||
|
||||
$html .= '</div>'.$this->options->eol;
|
||||
}
|
||||
|
||||
$html .= '</div>'.$this->options->eol;
|
||||
|
||||
// wrap the snippet into a body when saving to file
|
||||
if($saveToFile){
|
||||
$html = sprintf(
|
||||
'<!DOCTYPE html><head><meta charset="UTF-8"><title>QR Code</title></head><body>%s</body>',
|
||||
$this->options->eol.$html
|
||||
);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
/**
|
||||
* Class QRMarkupSVG
|
||||
*
|
||||
* @created 06.06.2022
|
||||
* @author smiley <smiley@chillerlan.net>
|
||||
* @copyright 2022 smiley
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
namespace chillerlan\QRCode\Output;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use function array_chunk, implode, sprintf;
|
||||
|
||||
/**
|
||||
* SVG output
|
||||
*
|
||||
* @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/
|
||||
* @see http://apex.infogridpacific.com/SVG/svg-tutorial-contents.html
|
||||
*/
|
||||
class QRMarkupSVG extends QRMarkup{
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function createMarkup(bool $saveToFile):string{
|
||||
$svg = $this->header();
|
||||
|
||||
if(!empty($this->options->svgDefs)){
|
||||
$svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
|
||||
}
|
||||
|
||||
$svg .= $this->paths();
|
||||
|
||||
// close svg
|
||||
$svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
|
||||
|
||||
// transform to data URI only when not saving to file
|
||||
if(!$saveToFile && $this->options->imageBase64){
|
||||
$svg = $this->base64encode($svg, 'image/svg+xml');
|
||||
}
|
||||
|
||||
return $svg;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the <svg> header with the given options parsed
|
||||
*/
|
||||
protected function header():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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns one or more SVG <path> elements
|
||||
*
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
|
||||
*/
|
||||
protected function paths():string{
|
||||
$paths = $this->collectModules(fn(int $x, int $y):string => $this->module($x, $y));
|
||||
$svg = [];
|
||||
|
||||
// create the path elements
|
||||
foreach($paths as $M_TYPE => $modules){
|
||||
// limit the total line length
|
||||
$chunks = array_chunk($modules, 100);
|
||||
$chonks = [];
|
||||
|
||||
foreach($chunks as $chunk){
|
||||
$chonks[] = implode(' ', $chunk);
|
||||
}
|
||||
|
||||
$path = implode($this->options->eol, $chonks);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return implode($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 module(int $x, int $y):string{
|
||||
|
||||
if($this->options->imageTransparent && !$this->matrix->check($x, $y)){
|
||||
return '';
|
||||
}
|
||||
|
||||
if($this->options->drawCircularModules && $this->matrix->checkTypeNotIn($x, $y, $this->options->keepAsSquare)){
|
||||
$r = $this->options->circleRadius;
|
||||
|
||||
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 sprintf('M%1$s %2$s h1 v1 h-1Z', $x, $y);
|
||||
}
|
||||
|
||||
}
|
||||
+5
-3
@@ -13,7 +13,9 @@ namespace chillerlan\QRCode;
|
||||
use chillerlan\QRCode\Common\{EccLevel, ECICharset, MaskPattern, Mode};
|
||||
use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Kanji, Number, QRCodeDataException, QRData, QRDataModeInterface, QRMatrix};
|
||||
use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, LuminanceSourceInterface};
|
||||
use chillerlan\QRCode\Output\{QRCodeOutputException, QRFpdf, QRGdImage, QRImagick, QRMarkup, QREps, QROutputInterface, QRString};
|
||||
use chillerlan\QRCode\Output\{
|
||||
QRCodeOutputException, QRFpdf, QRGdImage, QRImagick, QRMarkupHTML, QRMarkupSVG, QREps, QROutputInterface, QRString
|
||||
};
|
||||
use chillerlan\Settings\SettingsContainerInterface;
|
||||
use function class_exists, class_implements, in_array, mb_convert_encoding, mb_detect_encoding;
|
||||
|
||||
@@ -90,8 +92,8 @@ class QRCode{
|
||||
* @var string[]
|
||||
*/
|
||||
public const OUTPUT_MODES = [
|
||||
self::OUTPUT_MARKUP_SVG => QRMarkup::class,
|
||||
self::OUTPUT_MARKUP_HTML => QRMarkup::class,
|
||||
self::OUTPUT_MARKUP_SVG => QRMarkupSVG::class,
|
||||
self::OUTPUT_MARKUP_HTML => QRMarkupHTML::class,
|
||||
self::OUTPUT_IMAGE_PNG => QRGdImage::class,
|
||||
self::OUTPUT_IMAGE_GIF => QRGdImage::class,
|
||||
self::OUTPUT_IMAGE_JPG => QRGdImage::class,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
namespace chillerlan\QRCodeTest\Output;
|
||||
|
||||
use chillerlan\QRCode\Output\QRMarkupHTML;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
|
||||
/**
|
||||
@@ -17,6 +18,7 @@ use chillerlan\QRCode\QRCode;
|
||||
*/
|
||||
final class QRMarkupHTMLTest extends QRMarkupTestAbstract{
|
||||
|
||||
protected string $FQN = QRMarkupHTML::class;
|
||||
protected string $type = QRCode::OUTPUT_MARKUP_HTML;
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
namespace chillerlan\QRCodeTest\Output;
|
||||
|
||||
use chillerlan\QRCode\Output\QRMarkupSVG;
|
||||
use chillerlan\QRCode\QRCode;
|
||||
|
||||
/**
|
||||
@@ -17,6 +18,7 @@ use chillerlan\QRCode\QRCode;
|
||||
*/
|
||||
final class QRMarkupSVGTest extends QRMarkupTestAbstract{
|
||||
|
||||
protected string $FQN = QRMarkupSVG::class;
|
||||
protected string $type = QRCode::OUTPUT_MARKUP_SVG;
|
||||
|
||||
}
|
||||
|
||||
@@ -11,15 +11,12 @@
|
||||
namespace chillerlan\QRCodeTest\Output;
|
||||
|
||||
use chillerlan\QRCode\Data\QRMatrix;
|
||||
use chillerlan\QRCode\Output\QRMarkup;
|
||||
|
||||
/**
|
||||
* Tests the QRMarkup output module
|
||||
*/
|
||||
abstract class QRMarkupTestAbstract extends QROutputTestAbstract{
|
||||
|
||||
protected string $FQN = QRMarkup::class;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user