Просмотр исходного кода

:sparkles: added PDF support (#49)

codemasher 5 лет назад
Родитель
Сommit
c8fcb2d043
8 измененных файлов с 262 добавлено и 8 удалено
  1. 6 2
      README.md
  2. 4 4
      composer.json
  3. 46 0
      examples/fpdf.php
  4. 102 0
      src/Output/QRFpdf.php
  5. 5 1
      src/QRCode.php
  6. 2 0
      src/QROptions.php
  7. 23 1
      src/QROptionsTrait.php
  8. 74 0
      tests/Output/QRFpdfTest.php

+ 6 - 2
README.md

@@ -34,8 +34,11 @@ namespaced, cleaned up, improved and other stuff.
 
 ### Requirements
 - PHP 7.2+
-  - `ext-gd`, `ext-json`, `ext-mbstring`
-  - optional `ext-imagick`
+  - `ext-mbstring`
+  - optional: 
+     - `ext-json`, `ext-gd`
+     - `ext-imagick` with [ImageMagick](https://imagemagick.org) installed
+     - [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module
 
 ### Installation
 **requires [composer](https://getcomposer.org)**
@@ -289,6 +292,7 @@ name | description
 `OUTPUT_IMAGE_PNG`, `OUTPUT_IMAGE_JPG`, `OUTPUT_IMAGE_GIF` | `QROptions::$outputType` image
 `OUTPUT_STRING_JSON`, `OUTPUT_STRING_TEXT` | `QROptions::$outputType` string
 `OUTPUT_IMAGICK` | `QROptions::$outputType` ImageMagick
+`OUTPUT_FPDF` | `QROptions::$outputType` PDF, using [FPDF](https://github.com/setasign/fpdf)
 `OUTPUT_CUSTOM` | `QROptions::$outputType`, requires `QROptions::$outputInterface`
 `ECC_L`, `ECC_M`, `ECC_Q`, `ECC_H`, | ECC-Level: 7%, 15%, 25%, 30%  in `QROptions::$eccLevel`
 `DATA_NUMBER`, `DATA_ALPHANUM`, `DATA_BYTE`, `DATA_KANJI` | `QRDataInterface::$datamode`

+ 4 - 4
composer.json

@@ -25,16 +25,16 @@
 	],
 	"require": {
 		"php": "^7.2",
-		"ext-gd": "*",
-		"ext-json": "*",
 		"ext-mbstring": "*",
 		"chillerlan/php-settings-container": "^1.2"
 	},
 	"require-dev": {
-		"phpunit/phpunit": "^8.5"
+		"phpunit/phpunit": "^8.5",
+		"setasign/fpdf": "^1.8.2"
 	},
 	"suggest": {
-		"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps."
+		"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
+		"setasign/fpdf": "Required to use the QR FPDF output."
 	},
 	"autoload": {
 		"psr-4": {

+ 46 - 0
examples/fpdf.php

@@ -0,0 +1,46 @@
+<?php
+
+namespace chillerlan\QRCodeExamples;
+
+use chillerlan\QRCode\{QRCode, QROptions};
+
+require_once __DIR__ . '/../vendor/autoload.php';
+
+$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
+
+$options = new QROptions([
+    'version'      => 7,
+    'outputType'   => QRCode::OUTPUT_FPDF,
+    'eccLevel'     => QRCode::ECC_L,
+    'scale'        => 5,
+    'moduleValues' => [
+        // finder
+        1536 => [0, 63, 255], // dark (true)
+        6    => [255, 255, 255], // light (false), white is the transparency color and is enabled by default
+        // alignment
+        2560 => [255, 0, 255],
+        10   => [255, 255, 255],
+        // timing
+        3072 => [255, 0, 0],
+        12   => [255, 255, 255],
+        // format
+        3584 => [67, 191, 84],
+        14   => [255, 255, 255],
+        // version
+        4096 => [62, 174, 190],
+        16   => [255, 255, 255],
+        // data
+        1024 => [0, 0, 0],
+        4    => [255, 255, 255],
+        // darkmodule
+        512  => [0, 0, 0],
+        // separator
+        8    => [255, 255, 255],
+        // quietzone
+        18   => [255, 255, 255],
+    ],
+]);
+
+\header('Content-type: application/pdf');
+
+echo (new QRCode($options))->render($data);

+ 102 - 0
src/Output/QRFpdf.php

@@ -0,0 +1,102 @@
+<?php
+/**
+ * Class QRFpdf
+ *
+ * https://github.com/chillerlan/php-qrcode/pull/49
+ *
+ * @filesource   QRFpdf.php
+ * @created      03.06.2020
+ * @package      chillerlan\QRCode\Output
+ * @author       Maximilian Kresse
+ *
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCode\Output;
+
+use chillerlan\QRCode\Data\QRMatrix;
+use chillerlan\QRCode\QRCodeException;
+use chillerlan\Settings\SettingsContainerInterface;
+use FPDF;
+
+use function array_values, class_exists, count, is_array;
+
+/**
+ * QRFpdf output module (requires fpdf)
+ *
+ * @see https://github.com/Setasign/FPDF
+ * @see http://www.fpdf.org/
+ */
+class QRFpdf extends QROutputAbstract{
+
+	public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
+
+		if(!class_exists(FPDF::class)){
+			// @codeCoverageIgnoreStart
+			throw new QRCodeException(
+				'The QRFpdf output requires FPDF as dependency but the class "\FPDF" couldn\'t be found.'
+			);
+			// @codeCoverageIgnoreEnd
+		}
+
+		parent::__construct($options, $matrix);
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	protected function setModuleValues():void{
+
+		foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
+			$v = $this->options->moduleValues[$M_TYPE] ?? null;
+
+			if(!is_array($v) || count($v) < 3){
+				$this->moduleValues[$M_TYPE] = $defaultValue
+					? [0, 0, 0]
+					: [255, 255, 255];
+			}
+			else{
+				$this->moduleValues[$M_TYPE] = array_values($v);
+			}
+
+		}
+
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function dump(string $file = null):string{
+		$file = $file ?? $this->options->cachefile;
+
+		$fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]);
+		$fpdf->AddPage();
+
+		$prevColor = null;
+
+		foreach($this->matrix->matrix() as $y => $row){
+
+			foreach($row as $x => $M_TYPE){
+				/** @var int $M_TYPE */
+				$color = $this->moduleValues[$M_TYPE];
+
+				if($prevColor === null || $prevColor !== $color){
+					$fpdf->SetFillColor(...$color);
+					$prevColor = $color;
+				}
+
+				$fpdf->Rect($x * $this->scale, $y * $this->scale, 1 * $this->scale, 1 * $this->scale, 'F');
+			}
+
+		}
+
+		$pdfData = $fpdf->Output('S');
+
+		if($file !== null){
+			$this->saveToFile($pdfData, $file);
+		}
+
+		return $pdfData;
+	}
+
+}

+ 5 - 1
src/QRCode.php

@@ -16,7 +16,7 @@ use chillerlan\QRCode\Data\{
 	MaskPatternTester, QRCodeDataException, QRDataInterface, QRMatrix
 };
 use chillerlan\QRCode\Output\{
-	QRCodeOutputException, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
+	QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
 };
 use chillerlan\Settings\SettingsContainerInterface;
 
@@ -42,6 +42,7 @@ class QRCode{
 	public const OUTPUT_STRING_JSON = 'json';
 	public const OUTPUT_STRING_TEXT = 'text';
 	public const OUTPUT_IMAGICK     = 'imagick';
+	public const OUTPUT_FPDF        = 'fpdf';
 	public const OUTPUT_CUSTOM      = 'custom';
 
 	public const VERSION_AUTO       = -1;
@@ -88,6 +89,9 @@ class QRCode{
 		QRImagick::class => [
 			self::OUTPUT_IMAGICK,
 		],
+		QRFpdf::class => [
+			self::OUTPUT_FPDF
+		]
 	];
 
 	/**

+ 2 - 0
src/QROptions.php

@@ -51,6 +51,8 @@ use chillerlan\Settings\SettingsContainerAbstract;
  * @property string $imagickFormat
  * @property string $imagickBG
  *
+ * @property string $fpdfMeasureUnit
+ *
  * @property array  $moduleValues
  */
 class QROptions extends SettingsContainerAbstract{

+ 23 - 1
src/QROptionsTrait.php

@@ -12,7 +12,7 @@
 
 namespace chillerlan\QRCode;
 
-use function array_values, count, is_array, is_numeric, max, min, sprintf;
+use function array_values, count, in_array, is_array, is_numeric, max, min, sprintf, strtolower;
 
 trait QROptionsTrait{
 
@@ -241,6 +241,13 @@ trait QROptionsTrait{
 	 */
 	protected $imagickBG = null;
 
+	/**
+	 * Measurement unit for FPDF output: pt, mm, cm, in (defaults to "pt")
+	 *
+	 * @see \FPDF::__construct()
+	 */
+	protected $fpdfMeasureUnit = 'pt';
+
 	/**
 	 * Module values map
 	 *
@@ -367,4 +374,19 @@ trait QROptionsTrait{
 
 	}
 
+	/**
+	 * sets the FPDF measurement unit
+	 *
+	 * @codeCoverageIgnore
+	 */
+	protected function set_fpdfMeasureUnit(string $unit):void{
+		$unit = strtolower($unit);
+
+		if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){
+			$this->fpdfMeasureUnit = $unit;
+		}
+
+		// @todo throw or ignore silently?
+	}
+
 }

+ 74 - 0
tests/Output/QRFpdfTest.php

@@ -0,0 +1,74 @@
+<?php
+/**
+ * Class QRFpdfTest
+ *
+ * @filesource   QRFpdfTest.php
+ * @created      03.06.2020
+ * @package      chillerlan\QRCodeTest\Output
+ * @author       smiley <smiley@chillerlan.net>
+ * @copyright    2020 smiley
+ * @license      MIT
+ */
+
+namespace chillerlan\QRCodeTest\Output;
+
+use FPDF;
+use chillerlan\QRCode\Output\{QRFpdf, QROutputInterface};
+use chillerlan\QRCode\{QRCode, QROptions};
+
+use function class_exists, substr;
+
+/**
+ * Tests the QRFpdf output module
+ */
+class QRFpdfTest extends QROutputTestAbstract{
+
+	protected $FQCN = QRFpdf::class;
+
+	/**
+	 * @inheritDoc
+	 * @internal
+	 */
+	public function setUp():void{
+
+		if(!class_exists(FPDF::class)){
+			$this->markTestSkipped('FPDF not available');
+			return;
+		}
+
+		parent::setUp();
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function testSetModuleValues():void{
+
+		$this->options->moduleValues = [
+			// data
+			1024 => [0, 0, 0],
+			4    => [255, 255, 255],
+		];
+
+		$this->outputInterface->dump();
+
+		$this::assertTrue(true); // tricking the code coverage
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	public function testRenderImage():void{
+		$type = QRCode::OUTPUT_FPDF;
+
+		$this->options->outputType = $type;
+		$this->outputInterface->dump($this::cachefile.$type);
+
+		// substr() to avoid CreationDate
+		$expected = substr(file_get_contents($this::cachefile.$type), 0, 2000);
+		$actual   = substr($this->outputInterface->dump(), 0, 2000);
+
+		$this::assertSame($expected, $actual);
+	}
+
+}