PHP Classes

File: src/Contracts/Customs/Drawers/SvgDrawer.php

Recommend this page to a friend!
  Packages of Amirreza Ebrahimi   HeroQR Powerful PHP QR Code Library to generate PNG, SVG, PDF, Logos Ready to Use with Laravel   src/Contracts/Customs/Drawers/SvgDrawer.php   Download  
File: src/Contracts/Customs/Drawers/SvgDrawer.php
Role: Class source
Content type: text/plain
Description: Class source
Class: HeroQR Powerful PHP QR Code Library to generate PNG, SVG, PDF, Logos Ready to Use with Laravel
Generate QR code images in several formats
Author: By
Last change:
Date: 23 days ago
Size: 10,194 bytes
 

Contents

Class file image Download
<?php

declare(strict_types=1);

namespace
HeroQR\Contracts\Customs\Drawers;

use
Endroid\QrCode\{Color\ColorInterface,
   
Logo\LogoInterface,
   
Matrix\MatrixInterface,
   
QrCodeInterface};
use
HeroQR\Customs\{ImageOverlay, ShapeDrawers\SvgShapeDrawer};

/**
 * Renders QR codes as SVG images using GD-based processing.
 *
 * Provides methods for drawing QR code matrices, applying overlays,
 * and preparing corner images. Serves as a base for SVG QR code renderers.
 */
final class SvgDrawer extends AbstractDrawer
{
   
/**
     * Draws the QR code on the base image as an SVG.
     *
     * Renders the QR code matrix with optional corner images, block shape,
     * and image overlay. Additional options can be passed via $args.
     *
     * @param MatrixInterface $matrix The QR code matrix
     * @param QrCodeInterface $qrCode The QR code object containing colors
     * @param LogoInterface|null $logo Optional logo to embed in the QR code
     * @param ImageOverlay $imageOverlay Optional image overlay
     * @param string $blockShape The shape of QR code blocks (square, diamond, star)
     * @param mixed ...$args Optional additional arguments (currently expects [$options])
     * @return \SimpleXMLElement The generated SVG element
     * @throws \Exception
     */
   
public static function drawQrCode(
       
MatrixInterface $matrix,
       
QrCodeInterface $qrCode,
        ?
LogoInterface $logo,
       
ImageOverlay $imageOverlay,
       
string $blockShape,
       
mixed ...$args
   
): \SimpleXMLElement
   
{
        [
$baseBlockSize] = $args;

       
$baseImage = self::createBaseImage($matrix, [
           
'backgroundColor' => $qrCode->getBackgroundColor(),
           
'baseBlockSize' => $baseBlockSize,
        ]);

       
$foregroundColor = $qrCode->getForegroundColor();
       
$cornerImageUri = $imageOverlay->getUriImage($foregroundColor);

       
// ???????? ??? ???? ??? ?????? (?????? ????? PNG)
       
self::drawMatrix($baseImage->g, $matrix, $baseBlockSize, $foregroundColor, $cornerImageUri, $blockShape, $qrCode->getData(), $logo);

        return
$baseImage;
    }

   
/**
     * Creates the base image for the QR code
     *
     * @param MatrixInterface $matrix The matrix that defines the block layout for the QR code
     * @param array $options Options for creating the base image
     * @return \SimpleXMLElement The created base image resource
     */


   
protected static function createBaseImage(MatrixInterface $matrix, array $options = []): \SimpleXMLElement
   
{
       
$blockPixel = $options['baseBlockSize'];
       
$marginSize = $matrix->getMarginLeft();
       
$totalSize = ($matrix->getBlockCount() * $blockPixel) + ($marginSize * 2);

       
$xml = new \SimpleXMLElement('<svg xmlns="http://www.w3.org/2000/svg"/>');
       
$xml->addAttribute('version', '1.1');
       
$xml->addAttribute('viewBox', "0 0 {$totalSize} {$totalSize}");

        if (empty(
$options['exclude_svg_width_and_height'])) {
           
$xml->addAttribute('width', "{$totalSize}px");
           
$xml->addAttribute('height', "{$totalSize}px");
        }

       
$background = $xml->addChild('rect');
       
$background->addAttribute('width', (string)$totalSize);
       
$background->addAttribute('height', (string)$totalSize);
       
parent::allocateColor($background, $options['backgroundColor']);

       
$group = $xml->addChild('g');
       
$group->addAttribute('transform', "translate({$marginSize}, {$marginSize})");

        return
$xml;
    }

   
/**
     * Draws the QR code matrix onto the base image.
     *
     * @param mixed $baseImage The base image for the QR code.
     * @param MatrixInterface $matrix The QR code matrix.
     * @param int $baseBlockSize The pixel size of each QR matrix block.
     * @param int|ColorInterface $foregroundColor The fill color for the blocks.
     * @param string $resizedCorner This is not used in SVG, so it's not type hinted
     * @param string $blockShape The shape of the block in the QR code
     * @param LogoInterface|null $logo The logo to embed in the QR code (optional).
     * @param string $data The data encoded in the QR code (optional).
     * @return void
     */
   
protected static function drawMatrix(
       
mixed $baseImage,
       
MatrixInterface $matrix,
       
int $baseBlockSize,
       
int|ColorInterface $foregroundColor,
       
mixed $resizedCorner,
       
string $blockShape,
       
string $data,
        ?
LogoInterface $logo = null
   
): void
   
{
       
$blockCount = $matrix->getBlockCount();

       
$defs = self::prepareDefinitions($baseImage, $resizedCorner, $blockShape, $baseBlockSize, $foregroundColor);

       
$logoBounds = self::prepareLogoBounds($matrix, $logo, $data, $baseBlockSize);

        for (
$rowIndex = 0; $rowIndex < $blockCount; $rowIndex++) {
            for (
$columnIndex = 0; $columnIndex < $blockCount; $columnIndex++) {

                if (
parent::isWithinLogoBounds(array_merge(['rowIndex' => $rowIndex, 'columnIndex' => $columnIndex], $logoBounds))) {
                    continue;
                }

                if (
$matrix->getBlockValue($rowIndex, $columnIndex) === 1) {
                   
self::drawMatrixBlock($baseImage, $matrix, $rowIndex, $columnIndex, $baseBlockSize, $foregroundColor, $blockShape, '#corner_asset');
                }
            }
        }
    }

   
/**
     * Draws a block of the matrix on the QR code image, handling special corner blocks and filling others with a foreground color
     *
     * @param \SimpleXMLElement $baseImage The base image where the block will be drawn.
     * @param MatrixInterface $matrix The matrix representing the QR code.
     * @param int $rowIndex The row index of the block.
     * @param int $columnIndex The column index of the block.
     * @param ColorInterface $foregroundColor The color to fill the block.
     * @param string $blockShape The shape of the block in the QR code.
     * @param string $resizedCorner The resized corner image (used for corner blocks).
     * @return void
     */
   
private static function drawMatrixBlock(
        \
SimpleXMLElement $baseImage,
       
MatrixInterface $matrix,
       
int $rowIndex,
       
int $columnIndex,
       
int $baseBlockSize,
       
int|ColorInterface $foregroundColor,
       
string $blockShape,
       
string $resizedCorner
   
): void
   
{
       
$blockCount = $matrix->getBlockCount();

        if (
parent::isFinderPattern($rowIndex, $columnIndex, $blockCount)) {
           
self::drawCornerBlock($baseImage, $rowIndex, $columnIndex, $blockCount, $baseBlockSize, $resizedCorner);
        } else {
           
SvgShapeDrawer::{$blockShape}($baseImage, $rowIndex, $columnIndex, $baseBlockSize, $foregroundColor);
        }
    }

   
/**
     * Position and rotate QR finder patterns (corners) in the SVG
     *
     * @param \SimpleXMLElement $baseImage The SVG element to append the corner image to.
     * @param int $rowIndex The current row index in the QR matrix.
     * @param int $columnIndex The current column index in the QR matrix.
     * @param int $blockCount Total number of blocks in the QR matrix (width/height).
     * @param int $baseBlockSize The size (in pixels) of a single QR module.
     * @param string $resizedCorner The URI or path to the corner image asset.
     *
     * @return void
     */
   
protected static function drawCornerBlock(
        \
SimpleXMLElement $baseImage,
       
int $rowIndex,
       
int $columnIndex,
       
int $blockCount,
       
int $baseBlockSize,
       
string $resizedCorner
   
): void
   
{
        if (!
parent::isFinderStart($rowIndex, $columnIndex, $blockCount)) return;

       
$x = ($baseBlockSize * $columnIndex);
       
$y = ($baseBlockSize * $rowIndex);
       
$pixelSize = $baseBlockSize * self::FINDER_PATTERN_SIZE;

       
$use = $baseImage->addChild('use');
       
$use->addAttribute('href', $resizedCorner);
       
$use->addAttribute('x', (string)$x);
       
$use->addAttribute('y', (string)$y);

       
$rotation = parent::getFinderRotation($rowIndex, $columnIndex);
        if (
$rotation !== 0) {
           
$cx = $x + ($pixelSize / 2);
           
$cy = $y + ($pixelSize / 2);
           
$use->addAttribute('transform', "rotate({$rotation} {$cx} {$cy})");
        }
    }
    private static function
prepareDefinitions(\SimpleXMLElement $baseImage, $cornerUri, $shape,int $size, $color): \SimpleXMLElement
   
{
       
$root = dom_import_simplexml($baseImage)->ownerDocument;
       
$svgElement = simplexml_import_dom($root);
       
$defs = $svgElement->addChild('defs');

       
// ????? ????? ????
       
$pixelSize = $size * self::FINDER_PATTERN_SIZE;
       
$imgDef = $defs->addChild('image');
       
$imgDef->addAttribute('id', 'corner_asset');
       
$imgDef->addAttribute('xlink:href', $cornerUri, 'http://www.w3.org/1999/xlink');
       
$imgDef->addAttribute('width', (string)$pixelSize);
       
$imgDef->addAttribute('height', (string)$pixelSize);

       
// ????? ??? ????
       
SvgShapeDrawer::{$shape}($defs, 0, 0, $size, $color);

        return
$defs;
    }

   
/**
     * ????? ???? ???? ????
     */
   
private static function prepareLogoBounds(MatrixInterface $matrix, ?LogoInterface $logo, string $data, int $baseBlockSize): array
    {
       
$blockCount = $matrix->getBlockCount();
        [
$width, $height, $padding] = parent::prepareLogoDimensions($logo, $data, $blockCount, $baseBlockSize, SvgDrawer::class);

        [
$startRow, $endRow, $startCol, $endCol] = parent::calculateLogoBounds([
           
'blockCount' => $blockCount, 'width' => $width, 'height' => $height, 'padding' => $padding
       
]);

        return [
           
'logoStartRow' => intval($startRow),
           
'logoEndRow' => intval($endRow),
           
'logoStartCol' => intval($startCol),
           
'logoEndCol' => intval($endCol)
        ];
    }
}