Skip to content

Custom Renderers

The payload builder has no dependency on the image layer, so swapping the drawing is clean.

The Contract

php
namespace RealRashid\RaastQr\Renderers;

interface Renderer
{
    public function png(string $payload, array $options = []): string;

    public function svg(string $payload, array $options = []): string;
}

Two methods. png() returns raw bytes, svg() returns a document string.

Binding Your Own

php
// app/Providers/AppServiceProvider.php
use RealRashid\RaastQr\Renderers\Renderer;
use App\Support\BaconRenderer;

public function register(): void
{
    $this->app->bind(Renderer::class, BaconRenderer::class);
}

Everything else — the builder, the facade, the Blade component, the Artisan commands — keeps working unchanged.

The Options Array

Whatever you accept is up to you, but honouring these keeps your renderer a drop-in replacement:

KeyTypeMeaning
sizeintEdge length in pixels
marginintQuiet zone in modules
error_correctionstringL, M, Q or H
foregroundstring|arrayDark module colour
backgroundstring|arrayBackground colour
logoarray['path' => …, 'size' => …, 'punchout' => bool]
labelarray['text' => …, 'size' => …, 'font' => …]

Example: Simple QrCode

php
namespace App\Support;

use RealRashid\RaastQr\Renderers\Renderer;
use SimpleSoftwareIO\QrCode\Facades\QrCode;

class SimpleQrCodeRenderer implements Renderer
{
    public function png(string $payload, array $options = []): string
    {
        return (string) $this->base($options)->format('png')->generate($payload);
    }

    public function svg(string $payload, array $options = []): string
    {
        return (string) $this->base($options)->format('svg')->generate($payload);
    }

    protected function base(array $options)
    {
        [$r, $g, $b] = $this->rgb($options['foreground'] ?? '#000000');

        return QrCode::size($options['size'] ?? 900)
            ->margin($options['margin'] ?? 4)
            ->errorCorrection($options['error_correction'] ?? 'H')
            ->color($r, $g, $b);
    }

    protected function rgb(string $hex): array
    {
        $hex = ltrim($hex, '#');

        return [
            (int) hexdec(substr($hex, 0, 2)),
            (int) hexdec(substr($hex, 2, 2)),
            (int) hexdec(substr($hex, 4, 2)),
        ];
    }
}

Throwing Properly

Wrap failures in the package's own exception so callers can handle them uniformly:

php
use RealRashid\RaastQr\Exceptions\RaastQrException;

try {
    return $this->draw($payload, $options);
} catch (\Throwable $e) {
    throw RaastQrException::rendererFailure(
        'Could not render the QR code: ' . $e->getMessage()
    );
}

See Handling Errors.

Faking It in Tests

A no-op renderer keeps image generation out of tests that don't care about pixels:

php
use RealRashid\RaastQr\Renderers\Renderer;

class FakeRenderer implements Renderer
{
    public function png(string $payload, array $options = []): string
    {
        return 'png:' . $payload;
    }

    public function svg(string $payload, array $options = []): string
    {
        return '<svg data-payload="' . $payload . '"></svg>';
    }
}
php
$this->app->bind(Renderer::class, FakeRenderer::class);

expect(RaastQr::iban($iban)->toSvg())->toContain('0002020102110202000424');

Fast, and it asserts the thing that actually matters — the payload.


Made with ❤️ from Pakistan