added PDF support (#49)

This commit is contained in:
codemasher
2020-06-04 18:47:37 +02:00
parent abb7ce7831
commit c8fcb2d043
8 changed files with 262 additions and 8 deletions
+6 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}