Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ It also features a QR Code reader based on a [PHP port](https://github.com/khana
- String types: JSON, plain text, etc.
- Encapsulated Postscript (EPS)
- PDF via [FPDF](https://github.com/setasign/fpdf)
- Portable Bitmap ([PBM](https://en.wikipedia.org/wiki/Netpbm))
Comment thread
wgevaert marked this conversation as resolved.
- QR Code reader (via GD and ImageMagick)


Expand Down
1 change: 1 addition & 0 deletions docs/Usage/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ It also features a QR Code reader based on a [PHP port](https://github.com/khana
- String types: JSON, plain text, etc.
- Encapsulated Postscript (EPS)
- PDF via [FPDF](https://github.com/setasign/fpdf)
- Portable Bitmap ([PBM](https://en.wikipedia.org/wiki/Netpbm))
- QR Code reader (via GD and ImageMagick)


Expand Down
56 changes: 56 additions & 0 deletions src/Output/QRNetpbmBitmapAbstract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php
/**
* Class QRPbm
*
* @created 11.12.2025
* @author wgevaert
* @copyright 2025 wgevaert
* @license MIT
*/
declare(strict_types=1);

namespace chillerlan\QRCode\Output;

use chillerlan\QRCode\Output\QROutputAbstract;
use UnexpectedValueException;

abstract class QRNetpbmBitmapAbstract extends QROutputAbstract {

protected function prepareModuleValue( mixed $value ): mixed {
if ( !is_bool( $value ) ) {
throw new UnexpectedValueException( 'Expected boolean module value' );
}
return $value;
}

protected function getDefaultModuleValue( bool $isDark ): bool {
return $isDark;
}

public static function moduleValueIsValid( mixed $value ): bool {
return is_bool( $value );
}

abstract protected function getHeader(): string;

protected function getBody(): string {
$body = '';
foreach ($this->matrix->getBooleanMatrix() as $row) {
$line = '';
foreach ($row as $isDark) {
$line .= str_repeat( $isDark ? '1' : '0', $this->scale );
}
$line .= "\n";
$body .= str_repeat( $line, $this->scale );
}
return trim($body,"\n");
}

public function dump( string|null $file = null ): mixed {
$qrString = $this->getHeader()."\n"
.$this->length.' '.$this->length."\n".$this->getBody();
$this->saveToFile( $qrString, $file );

return $qrString;
}
}
20 changes: 20 additions & 0 deletions src/Output/QRNetpbmBitmapAscii.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php
/**
* Class QRPbm
*
* @created 11.12.2025
* @author wgevaert
* @copyright 2025 wgevaert
* @license MIT
*/
declare(strict_types=1);

namespace chillerlan\QRCode\Output;

use chillerlan\QRCode\Output\QRNetpbmBitmapAbstract;

class QRNetpbmBitmapAscii extends QRNetpbmBitmapAbstract {
protected function getHeader(): string {
return 'P1';
}
}
38 changes: 38 additions & 0 deletions src/Output/QRNetpbmBitmapBinary.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* Class QRPbm
*
* @created 11.12.2025
* @author wgevaert
* @copyright 2025 wgevaert
* @license MIT
*/
declare(strict_types=1);

namespace chillerlan\QRCode\Output;

use chillerlan\QRCode\Output\QRNetpbmBitmapAbstract;
Comment thread
wgevaert marked this conversation as resolved.
Outdated

class QRNetpbmBitmapBinary extends QRNetpbmBitmapAbstract {
protected function getHeader(): string {
return 'P4';
}

protected function getBody(): string {
$asciiBody = parent::getBody();
$body = '';
foreach (explode("\n", $asciiBody) as $row) {
$body .= $this->asciiBinToBinary( $row );
}
return $body;
}

private function asciiBinToBinary( string $asciiBin ): string {
$binaryString = '';
foreach(str_split( $asciiBin, 8 ) as $currentChunk) {
$currentChunk = str_pad( $currentChunk, 8, '0' );
$binaryString .= pack( 'C', bindec( $currentChunk ) );
}
return $binaryString;
}
}
58 changes: 58 additions & 0 deletions tests/Output/QRNetpbmBitmapAsciiTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* Class QRPbmTest
*
* @created 11.12.2025
* @author wgevaert
* @copyright 2025 wgevaert
* @license MIT
*/
declare(strict_types=1);

namespace chillerlan\QRCodeTest\Output;

use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\Output\{QROutputInterface, QRNetpbmBitmapAscii};
use chillerlan\Settings\SettingsContainerInterface;
use PHPUnit\Framework\Attributes\Test;

/**
* Tests the QRNetpbmBitmapAscii output class
*/
final class QRNetpbmBitmapAsciiTest extends QROutputTestAbstract {

protected function getOutputInterface(
SettingsContainerInterface|QROptions $options,
QRMatrix $matrix,
):QROutputInterface{
return new QRNetpbmBitmapAscii($options, $matrix);
}

/**
* @phpstan-return array<string, array{0: mixed, 1: bool}>
*/
public static function moduleValueProvider():array{
return [
'invalid: wrong type: array' => [[], false],
'invalid: wrong type: string' => ['abc', false],
'valid: true' => [true, true],
'valid: false' => [false, true],
];
}

#[Test]
public function setModuleValues():void{
$this->options->moduleValues = [
// data
QRMatrix::M_DATA_DARK => true,
QRMatrix::M_DATA => false,
];

$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
$data = $this->outputInterface->dump();

$this::assertStringContainsString('1', $data);
$this::assertStringContainsString('0', $data);
}
}
57 changes: 57 additions & 0 deletions tests/Output/QRNetpbmBitmapBinaryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php
/**
* Class QRPbmTest
*
* @created 11.12.2025
* @author wgevaert
* @copyright 2025 wgevaert
* @license MIT
*/
declare(strict_types=1);

namespace chillerlan\QRCodeTest\Output;

use chillerlan\QRCode\QROptions;
use chillerlan\QRCode\Data\QRMatrix;
use chillerlan\QRCode\Output\{QROutputInterface, QRNetpbmBitmapBinary};
use chillerlan\Settings\SettingsContainerInterface;
use PHPUnit\Framework\Attributes\Test;

/**
* Tests the QRNetpbmBitmapBinary output class
*/
final class QRNetpbmBitmapBinaryTest extends QROutputTestAbstract {

protected function getOutputInterface(
SettingsContainerInterface|QROptions $options,
QRMatrix $matrix,
):QROutputInterface{
return new QRNetpbmBitmapBinary($options, $matrix);
}

/**
* @phpstan-return array<string, array{0: mixed, 1: bool}>
*/
public static function moduleValueProvider():array{
return [
'invalid: wrong type: array' => [[], false],
'invalid: wrong type: string' => ['abc', false],
'valid: true' => [true, true],
'valid: false' => [false, true],
];
}

#[Test]
public function setModuleValues():void{
$this->options->moduleValues = [
// data
QRMatrix::M_DATA_DARK => true,
QRMatrix::M_DATA => false,
];

$this->outputInterface = $this->getOutputInterface($this->options, $this->matrix);
$data = $this->outputInterface->dump();

$this::assertStringContainsString("\0", $data);
}
}