Skip to content

Commit c8fcb2d

Browse files
committed
✨ added PDF support (#49)
1 parent abb7ce7 commit c8fcb2d

File tree

8 files changed

+262
-8
lines changed

8 files changed

+262
-8
lines changed

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,11 @@ namespaced, cleaned up, improved and other stuff.
3434

3535
### Requirements
3636
- PHP 7.2+
37-
- `ext-gd`, `ext-json`, `ext-mbstring`
38-
- optional `ext-imagick`
37+
- `ext-mbstring`
38+
- optional:
39+
- `ext-json`, `ext-gd`
40+
- `ext-imagick` with [ImageMagick](https://imagemagick.org) installed
41+
- [`setasign/fpdf`](https://github.com/setasign/fpdf) for the PDF output module
3942

4043
### Installation
4144
**requires [composer](https://getcomposer.org)**
@@ -289,6 +292,7 @@ name | description
289292
`OUTPUT_IMAGE_PNG`, `OUTPUT_IMAGE_JPG`, `OUTPUT_IMAGE_GIF` | `QROptions::$outputType` image
290293
`OUTPUT_STRING_JSON`, `OUTPUT_STRING_TEXT` | `QROptions::$outputType` string
291294
`OUTPUT_IMAGICK` | `QROptions::$outputType` ImageMagick
295+
`OUTPUT_FPDF` | `QROptions::$outputType` PDF, using [FPDF](https://github.com/setasign/fpdf)
292296
`OUTPUT_CUSTOM` | `QROptions::$outputType`, requires `QROptions::$outputInterface`
293297
`ECC_L`, `ECC_M`, `ECC_Q`, `ECC_H`, | ECC-Level: 7%, 15%, 25%, 30% in `QROptions::$eccLevel`
294298
`DATA_NUMBER`, `DATA_ALPHANUM`, `DATA_BYTE`, `DATA_KANJI` | `QRDataInterface::$datamode`

composer.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@
2525
],
2626
"require": {
2727
"php": "^7.2",
28-
"ext-gd": "*",
29-
"ext-json": "*",
3028
"ext-mbstring": "*",
3129
"chillerlan/php-settings-container": "^1.2"
3230
},
3331
"require-dev": {
34-
"phpunit/phpunit": "^8.5"
32+
"phpunit/phpunit": "^8.5",
33+
"setasign/fpdf": "^1.8.2"
3534
},
3635
"suggest": {
37-
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps."
36+
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
37+
"setasign/fpdf": "Required to use the QR FPDF output."
3838
},
3939
"autoload": {
4040
"psr-4": {

examples/fpdf.php

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace chillerlan\QRCodeExamples;
4+
5+
use chillerlan\QRCode\{QRCode, QROptions};
6+
7+
require_once __DIR__ . '/../vendor/autoload.php';
8+
9+
$data = 'https://www.youtube.com/watch?v=DLzxrzFCyOs&t=43s';
10+
11+
$options = new QROptions([
12+
'version' => 7,
13+
'outputType' => QRCode::OUTPUT_FPDF,
14+
'eccLevel' => QRCode::ECC_L,
15+
'scale' => 5,
16+
'moduleValues' => [
17+
// finder
18+
1536 => [0, 63, 255], // dark (true)
19+
6 => [255, 255, 255], // light (false), white is the transparency color and is enabled by default
20+
// alignment
21+
2560 => [255, 0, 255],
22+
10 => [255, 255, 255],
23+
// timing
24+
3072 => [255, 0, 0],
25+
12 => [255, 255, 255],
26+
// format
27+
3584 => [67, 191, 84],
28+
14 => [255, 255, 255],
29+
// version
30+
4096 => [62, 174, 190],
31+
16 => [255, 255, 255],
32+
// data
33+
1024 => [0, 0, 0],
34+
4 => [255, 255, 255],
35+
// darkmodule
36+
512 => [0, 0, 0],
37+
// separator
38+
8 => [255, 255, 255],
39+
// quietzone
40+
18 => [255, 255, 255],
41+
],
42+
]);
43+
44+
\header('Content-type: application/pdf');
45+
46+
echo (new QRCode($options))->render($data);

src/Output/QRFpdf.php

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
<?php
2+
/**
3+
* Class QRFpdf
4+
*
5+
* https://github.com/chillerlan/php-qrcode/pull/49
6+
*
7+
* @filesource QRFpdf.php
8+
* @created 03.06.2020
9+
* @package chillerlan\QRCode\Output
10+
* @author Maximilian Kresse
11+
*
12+
* @license MIT
13+
*/
14+
15+
namespace chillerlan\QRCode\Output;
16+
17+
use chillerlan\QRCode\Data\QRMatrix;
18+
use chillerlan\QRCode\QRCodeException;
19+
use chillerlan\Settings\SettingsContainerInterface;
20+
use FPDF;
21+
22+
use function array_values, class_exists, count, is_array;
23+
24+
/**
25+
* QRFpdf output module (requires fpdf)
26+
*
27+
* @see https://github.com/Setasign/FPDF
28+
* @see http://www.fpdf.org/
29+
*/
30+
class QRFpdf extends QROutputAbstract{
31+
32+
public function __construct(SettingsContainerInterface $options, QRMatrix $matrix){
33+
34+
if(!class_exists(FPDF::class)){
35+
// @codeCoverageIgnoreStart
36+
throw new QRCodeException(
37+
'The QRFpdf output requires FPDF as dependency but the class "\FPDF" couldn\'t be found.'
38+
);
39+
// @codeCoverageIgnoreEnd
40+
}
41+
42+
parent::__construct($options, $matrix);
43+
}
44+
45+
/**
46+
* @inheritDoc
47+
*/
48+
protected function setModuleValues():void{
49+
50+
foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
51+
$v = $this->options->moduleValues[$M_TYPE] ?? null;
52+
53+
if(!is_array($v) || count($v) < 3){
54+
$this->moduleValues[$M_TYPE] = $defaultValue
55+
? [0, 0, 0]
56+
: [255, 255, 255];
57+
}
58+
else{
59+
$this->moduleValues[$M_TYPE] = array_values($v);
60+
}
61+
62+
}
63+
64+
}
65+
66+
/**
67+
* @inheritDoc
68+
*/
69+
public function dump(string $file = null):string{
70+
$file = $file ?? $this->options->cachefile;
71+
72+
$fpdf = new FPDF('P', $this->options->fpdfMeasureUnit, [$this->length, $this->length]);
73+
$fpdf->AddPage();
74+
75+
$prevColor = null;
76+
77+
foreach($this->matrix->matrix() as $y => $row){
78+
79+
foreach($row as $x => $M_TYPE){
80+
/** @var int $M_TYPE */
81+
$color = $this->moduleValues[$M_TYPE];
82+
83+
if($prevColor === null || $prevColor !== $color){
84+
$fpdf->SetFillColor(...$color);
85+
$prevColor = $color;
86+
}
87+
88+
$fpdf->Rect($x * $this->scale, $y * $this->scale, 1 * $this->scale, 1 * $this->scale, 'F');
89+
}
90+
91+
}
92+
93+
$pdfData = $fpdf->Output('S');
94+
95+
if($file !== null){
96+
$this->saveToFile($pdfData, $file);
97+
}
98+
99+
return $pdfData;
100+
}
101+
102+
}

src/QRCode.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
MaskPatternTester, QRCodeDataException, QRDataInterface, QRMatrix
1717
};
1818
use chillerlan\QRCode\Output\{
19-
QRCodeOutputException, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
19+
QRCodeOutputException, QRFpdf, QRImage, QRImagick, QRMarkup, QROutputInterface, QRString
2020
};
2121
use chillerlan\Settings\SettingsContainerInterface;
2222

@@ -42,6 +42,7 @@ class QRCode{
4242
public const OUTPUT_STRING_JSON = 'json';
4343
public const OUTPUT_STRING_TEXT = 'text';
4444
public const OUTPUT_IMAGICK = 'imagick';
45+
public const OUTPUT_FPDF = 'fpdf';
4546
public const OUTPUT_CUSTOM = 'custom';
4647

4748
public const VERSION_AUTO = -1;
@@ -88,6 +89,9 @@ class QRCode{
8889
QRImagick::class => [
8990
self::OUTPUT_IMAGICK,
9091
],
92+
QRFpdf::class => [
93+
self::OUTPUT_FPDF
94+
]
9195
];
9296

9397
/**

src/QROptions.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@
5151
* @property string $imagickFormat
5252
* @property string $imagickBG
5353
*
54+
* @property string $fpdfMeasureUnit
55+
*
5456
* @property array $moduleValues
5557
*/
5658
class QROptions extends SettingsContainerAbstract{

src/QROptionsTrait.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
namespace chillerlan\QRCode;
1414

15-
use function array_values, count, is_array, is_numeric, max, min, sprintf;
15+
use function array_values, count, in_array, is_array, is_numeric, max, min, sprintf, strtolower;
1616

1717
trait QROptionsTrait{
1818

@@ -241,6 +241,13 @@ trait QROptionsTrait{
241241
*/
242242
protected $imagickBG = null;
243243

244+
/**
245+
* Measurement unit for FPDF output: pt, mm, cm, in (defaults to "pt")
246+
*
247+
* @see \FPDF::__construct()
248+
*/
249+
protected $fpdfMeasureUnit = 'pt';
250+
244251
/**
245252
* Module values map
246253
*
@@ -367,4 +374,19 @@ protected function set_version(int $version):void{
367374

368375
}
369376

377+
/**
378+
* sets the FPDF measurement unit
379+
*
380+
* @codeCoverageIgnore
381+
*/
382+
protected function set_fpdfMeasureUnit(string $unit):void{
383+
$unit = strtolower($unit);
384+
385+
if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){
386+
$this->fpdfMeasureUnit = $unit;
387+
}
388+
389+
// @todo throw or ignore silently?
390+
}
391+
370392
}

tests/Output/QRFpdfTest.php

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
/**
3+
* Class QRFpdfTest
4+
*
5+
* @filesource QRFpdfTest.php
6+
* @created 03.06.2020
7+
* @package chillerlan\QRCodeTest\Output
8+
* @author smiley <[email protected]>
9+
* @copyright 2020 smiley
10+
* @license MIT
11+
*/
12+
13+
namespace chillerlan\QRCodeTest\Output;
14+
15+
use FPDF;
16+
use chillerlan\QRCode\Output\{QRFpdf, QROutputInterface};
17+
use chillerlan\QRCode\{QRCode, QROptions};
18+
19+
use function class_exists, substr;
20+
21+
/**
22+
* Tests the QRFpdf output module
23+
*/
24+
class QRFpdfTest extends QROutputTestAbstract{
25+
26+
protected $FQCN = QRFpdf::class;
27+
28+
/**
29+
* @inheritDoc
30+
* @internal
31+
*/
32+
public function setUp():void{
33+
34+
if(!class_exists(FPDF::class)){
35+
$this->markTestSkipped('FPDF not available');
36+
return;
37+
}
38+
39+
parent::setUp();
40+
}
41+
42+
/**
43+
* @inheritDoc
44+
*/
45+
public function testSetModuleValues():void{
46+
47+
$this->options->moduleValues = [
48+
// data
49+
1024 => [0, 0, 0],
50+
4 => [255, 255, 255],
51+
];
52+
53+
$this->outputInterface->dump();
54+
55+
$this::assertTrue(true); // tricking the code coverage
56+
}
57+
58+
/**
59+
* @inheritDoc
60+
*/
61+
public function testRenderImage():void{
62+
$type = QRCode::OUTPUT_FPDF;
63+
64+
$this->options->outputType = $type;
65+
$this->outputInterface->dump($this::cachefile.$type);
66+
67+
// substr() to avoid CreationDate
68+
$expected = substr(file_get_contents($this::cachefile.$type), 0, 2000);
69+
$actual = substr($this->outputInterface->dump(), 0, 2000);
70+
71+
$this::assertSame($expected, $actual);
72+
}
73+
74+
}

0 commit comments

Comments
 (0)