博客园  :: 首页  :: 新随笔  :: 订阅 订阅  :: 管理

PHP 生成datamatrix二维码

Posted on 2025-10-30 16:59  PHP-张工  阅读(2)  评论(0)    收藏  举报
网上有js生成DM码的库:
PHP版本的找了一些,都太重了,功能太多。
所以利用豆包根据这个js库生成了PHP版本的。
效果如下:
datamatrix

PHP代码如下:

<?php

// 系统入口
date_default_timezone_set('PRC');
error_reporting(E_ALL^E_WARNING^E_NOTICE);
set_time_limit(10);

header('Content-Type: image/svg+xml');

/**
 * DataMatrix PHP Implementation
 * Based on datamatrix.js by Constantine (https://github.com/datalog/datamatrix-svg)
 * under MIT license
 */

class DATAMatrix
{
    private $M = []; // Matrix to hold the datamatrix bits
    private $xx = 0; // Width of the matrix
    private $yy = 0; // Height of the matrix
    private $q = []; // Configuration array

    /**
     * Constructor
     * @param mixed $Q Optional string message or configuration array
     */
    public function __construct($Q = '')
    {
        $this->q = []; // Initialize empty configuration
        $this->M = []; // Initialize empty matrix
        // If Q is provided, encode it
        if ($Q !== '') {
            $this->encodeMsg($Q);
        }
    }

    /**
     * Set a bit in the matrix
     * @param int $x X coordinate
     * @param int $y Y coordinate
     */
    private function bit($x, $y)
    {
        if (!isset($this->M[$y])) {
            $this->M[$y] = [];
        }
        $this->M[$y][$x] = 1;
    }

    /**
     * Convert text to ASCII encoding
     * @param string $t Text to encode
     * @return array Encoded data
     */
    private function toAscii($t)
    {
        $r = [];
        $l = strlen($t);

        for ($i = 0; $i < $l; $i++) {
            $c = ord($t[$i]);
            $c1 = ($i + 1 < $l) ? ord($t[$i + 1]) : 0;

            if ($c > 47 && $c < 58 && $c1 > 47 && $c1 < 58) { /* 2 digits */
                $r[] = ($c - 48) * 10 + $c1 + 82; /* - 48 + 130 = 82 */
                $i++;
            } else if ($c > 127) { /* extended char */
                $r[] = 235;
                $r[] = ($c - 127) & 255;
            } else {
                $r[] = $c + 1; /* char */
            }
        }

        return $r;
    }

    /**
     * Convert text to Base256 encoding
     * @param string $t Text to encode
     * @return array Encoded data
     */
    private function toBase($t)
    {
        $r = [231]; /* switch to Base 256 */
        $l = strlen($t);

        if (250 < $l) {
            $r[] = 37 + intval($l / 250) & 255; /* length high byte (in 255 state algo) */
        }

        $r[] = ($l % 250 + 149 * (count($r) + 1) % 255 + 1) & 255; /* length low byte (in 255 state algo) */

        for ($i = 0; $i < $l; $i++) {
            $r[] = (ord($t[$i]) + 149 * (count($r) + 1) % 255 + 1) & 255; /* data in 255 state algo */
        }

        return $r;
    }

    /**
     * Convert text to Edifact encoding
     * @param string $t Text to encode
     * @return array Encoded data
     */
    private function toEdifact($t)
    {
        $n = strlen($t);
        $l = ($n + 1) & -4;
        $cw = 0;
        $ch = 0;
        $r = ($l > 0) ? [240] : []; /* switch to Edifact */

        for ($i = 0; $i < $l; $i++) {
            if ($i < $l - 1) {
                /* encode char */
                $ch = ord($t[$i]);
                if ($ch < 32 || $ch > 94) return []; /* not in set */
            } else {
                $ch = 31; /* return to ASCII */
            }

            $cw = $cw * 64 + ($ch & 63);

            if (($i & 3) == 3) {
                /* 4 data in 3 words */
                $r[] = $cw >> 16;
                $r[] = ($cw >> 8) & 255;
                $r[] = $cw & 255;
                $cw = 0;
            }
        }

        return $l > $n ? $r : array_merge($r, $this->toAscii(substr($t, $l == 0 ? 0 : $l - 1))); /* last chars*/
    }

    /**
     * Convert text to C40/TEXT/X12 encoding
     * @param string $t Text to encode
     * @param array $s Character set definition
     * @return array Encoded data
     */
    private function toText($t, $s)
    {
        $i = 0;
        $j = 0;
        $cc = 0;
        $cw = 0;
        $l = strlen($t);
        $r = [$s[0]]; /* start switch */

        $push = function ($v) use (&$cc, &$cw, &$r, $s) {
            /* pack 3 chars in 2 codes */
            $cw = 40 * $cw + $v;

            /* add code */
            if ($cc++ == 2) {
                $r[] = ++$cw >> 8;
                $r[] = $cw & 255;
                $cc = $cw = 0;
            }
        };

        for ($i = 0; $i < $l; $i++) {
            /* last char in ASCII is shorter */
            if (0 == $cc && $i == $l - 1) break;

            $ch = ord($t[$i]);

            if ($ch > 127 && 238 != $r[0]) { /* extended char */
                $push(1);
                $push(30);
                $ch -= 128; /* hi bit in C40 & TEXT */
            }

            for ($j = 1; $ch > $s[$j]; $j += 3) {
            } /* select char set */

            $x = $s[$j + 1]; /* shift */

            if (8 == $x || (9 == $x && 0 == $cc && $i == $l - 1)) return []; /* char not in set or padding fails */

            if ($x < 5 && $cc == 2 && $i == $l - 1) break; /* last char in ASCII */
            if ($x < 5) $push($x); /* shift */

            $push($ch - $s[$j + 2]); /* char offset */
        }

        if (2 == $cc && 238 !== $r[0]) { /* add pad */
            $push(0);
        }

        $r[] = 254; /* return to ASCII */

        if ($cc > 0 || $i < $l) {
            $r = array_merge($r, $this->toAscii(substr($t, $i - $cc))); /* last chars */
        }

        return $r;
    }

    /**
     * Encode message and generate matrix
     * @param mixed $Q String message or configuration array
     */
    private function encodeMsg($Q)
    {
        // Store the original configuration for later use in getSVG
        if (is_string($Q)) {
            $this->q = ['msg' => $Q];
        } else {
            // Ensure q is always an array
            $this->q = is_array($Q) ? $Q : [];
        }

        $q = $this->q;
        $text = rawurldecode(urlencode($q['msg'] ?? ''));

        $M = [];

        $enc = $this->toAscii($text);
        $el = count($enc);

        // Try C40 encoding
        $k = $this->toText($text, [
            230,
            31,
            0,
            0,
            32,
            9,
            29,
            47,
            1,
            33,
            57,
            9,
            44,
            64,
            1,
            43,
            90,
            9,
            51,
            95,
            1,
            69,
            127,
            2,
            96,
            255,
            1,
            0
        ]);
        $l = count($k);
        if ($l > 0 && $l < $el) {
            $enc = $k;
            $el = $l;
        }

        // Try TEXT encoding
        $k = $this->toText($text, [
            239,
            31,
            0,
            0,
            32,
            9,
            29,
            47,
            1,
            33,
            57,
            9,
            44,
            64,
            1,
            43,
            90,
            2,
            64,
            95,
            1,
            69,
            122,
            9,
            83,
            127,
            2,
            96,
            255,
            1,
            0
        ]);
        $l = count($k);
        if ($l > 0 && $l < $el) {
            $enc = $k;
            $el = $l;
        }

        // Try X12 encoding
        $k = $this->toText($text, [
            238,
            12,
            8,
            0,
            13,
            9,
            13,
            31,
            8,
            0,
            32,
            9,
            29,
            41,
            8,
            0,
            42,
            9,
            41,
            47,
            8,
            0,
            57,
            9,
            44,
            64,
            8,
            0,
            90,
            9,
            51,
            255,
            8,
            0
        ]);
        $l = count($k);
        if ($l > 0 && $l < $el) {
            $enc = $k;
            $el = $l;
        }

        // Try Edifact encoding
        $k = $this->toEdifact($text);
        $l = count($k);
        if ($l > 0 && $l < $el) {
            $enc = $k;
            $el = $l;
        }

        // Try Base256 encoding
        $k = $this->toBase($text);
        $l = count($k);
        if ($l > 0 && $l < $el) {
            $enc = $k;
            $el = $l;
        }

        // Symbol size, regions, region size
        $h = 0;
        $w = 0;
        $nc = 1;
        $nr = 1;
        $fw = 0;
        $fh = 0;

        // Compute symbol size
        $i = 0;
        $j = -1;
        $c = 0;
        $r = 0;
        $s = 0;
        $b = 1;

        // Reed / solomon code
        $rs = array_fill(0, 70, 0);
        $rc = array_fill(0, 70, 0);

        // Log / exp table for multiplication
        $lg = array_fill(0, 256, 0);
        $ex = array_fill(0, 255, 0);

        $rct = isset($q['rct']) ? $q['rct'] : false;

        if ($rct && $el < 50) {
            // Rectangular symbols
            $k = [
                16,
                7,
                28,
                11,
                24,
                14,
                32,
                18,
                32,
                24,
                44,
                28
            ];

            do {
                $w = $k[++$j]; /* width */
                $h = 6 + ($j & 12); /* height */
                $l = $w * $h / 8; /* bytes count in symbol */
            } while ($l - $k[++$j] < $el); /* could we fill the rect? */

            // Column regions
            if ($w > 25) {
                $nc = 2;
            }
        } else {
            // Square symbols
            $w = $h = 6;
            $i = 2; /* size increment */
            $k = [5, 7, 10, 12, 14, 18, 20, 24, 28, 36, 42, 48, 56, 68, 84, 112, 144, 192, 224, 272, 336, 408, 496, 620]; /* rs checkwords */

            do {
                if (++$j == count($k)) return [0, 0]; /* msg is too long */

                if ($w > 11 * $i) {
                    $i = 4 + $i & 12; /* advance increment */
                }

                $w = $h += $i;
                $l = ($w * $h) >> 3;
            } while ($l - $k[$j] < $el);

            if ($w > 27) {
                $nr = $nc = 2 * intval($w / 54) + 2; /* regions */
            }
            if ($l > 255) {
                $b = 2 * intval($l >> 9) + 2; /* blocks */
            }
        }

        $s = $k[$j]; /* rs checkwords */
        $fw = $w / $nc; /* region size */
        $fh = $h / $nr;

        // First padding
        if ($el < $l - $s) {
            $enc[$el++] = 129;
        }

        // More padding
        while ($el < $l - $s) {
            $enc[$el++] = ((149 * $el) % 253 + 130) % 254;
        }

        // Reed Solomon error detection and correction
        $s /= $b;

        // Log / exp table of Galois field
        for ($j = 1, $i = 0; $i < 255; $i++) {
            $ex[$i] = $j;
            $lg[$j] = $i;
            $j += $j;

            if ($j > 255) {
                $j ^= 301; /* 301 == a^8 + a^5 + a^3 + a^2 + 1 */
            }
        }

        // RS generator polynomial
        for ($rs[$s] = 0, $i = 1; $i <= $s; $i++) {
            for ($j = $s - $i, $rs[$j] = 1; $j < $s; $j++) {
                $rs[$j] = $rs[$j + 1] ^ $ex[($lg[$rs[$j]] + $i) % 255];
            }
        }

        // RS correction data for each block
        for ($c = 0; $c < $b; $c++) {
            for ($i = 0; $i <= $s; $i++) {
                $rc[$i] = 0;
            }
            for ($i = $c; $i < $el; $i += $b) {
                for ($j = 0, $x = $rc[0] ^ $enc[$i]; $j < $s; $j++) {
                    $rc[$j] = $rc[$j + 1] ^ ($x ? $ex[($lg[$rs[$j]] + $lg[$x]) % 255] : 0);
                }
            }

            // Interleaved correction data
            for ($i = 0; $i < $s; $i++) {
                $enc[$el + $c + $i * $b] = $rc[$i];
            }
        }

        // Layout perimeter finder pattern
        // Horizontal
        for ($i = 0; $i < $h + 2 * $nr; $i += $fh + 2) {
            for ($j = 0; $j < $w + 2 * $nc; $j++) {
                $this->bit($j, $i + $fh + 1);
                if (($j & 1) == 0) {
                    $this->bit($j, $i);
                }
            }
        }

        // Vertical
        for ($i = 0; $i < $w + 2 * $nc; $i += $fw + 2) {
            for ($j = 0; $j < $h; $j++) {
                $this->bit($i, $j + intval($j / $fh) * 2 + 1);
                if (($j & 1) == 1) {
                    $this->bit($i + $fw + 1, $j + intval($j / $fh) * 2);
                }
            }
        }

        $s = 2; /* step */
        $c = 0; /* column */
        $r = 4; /* row */
        $b = [ /* nominal byte layout */
            0,
            0,
            -1,
            0,
            -2,
            0,
            0,
            -1,
            -1,
            -1,
            -2,
            -1,
            -1,
            -2,
            -2,
            -2
        ];

        // Diagonal steps
        for ($i = 0; $i < $l; $r -= $s, $c += $s) {
            if ($r == $h - 3 && $c == -1) {
                $k = [ /* corner A layout */
                    $w,
                    6 - $h,
                    $w,
                    5 - $h,
                    $w,
                    4 - $h,
                    $w,
                    3 - $h,
                    $w - 1,
                    3 - $h,
                    3,
                    2,
                    2,
                    2,
                    1,
                    2
                ];
            } else if ($r == $h + 1 && $c == 1 && ($w & 7) == 0 && ($h & 7) == 6) {
                $k = [ /* corner D layout */
                    $w - 2,
                    -$h,
                    $w - 3,
                    -$h,
                    $w - 4,
                    -$h,
                    $w - 2,
                    -1 - $h,
                    $w - 3,
                    -1 - $h,
                    $w - 4,
                    -1 - $h,
                    $w - 2,
                    -2,
                    -1,
                    -2
                ];
            } else {
                if ($r == 0 && $c == $w - 2 && ($w & 3)) continue; /* corner B: omit upper left */
                if ($r < 0 || $c >= $w || $r >= $h || $c < 0) { /* outside */
                    $s = -$s; /* turn around */
                    $r += 2 + $s / 2;
                    $c += 2 - $s / 2;

                    while ($r < 0 || $c >= $w || $r >= $h || $c < 0) {
                        $r -= $s;
                        $c += $s;
                    }
                }
                if ($r == $h - 2 && $c == 0 && ($w & 3)) {
                    $k = [ /* corner B layout */
                        $w - 1,
                        3 - $h,
                        $w - 1,
                        2 - $h,
                        $w - 2,
                        2 - $h,
                        $w - 3,
                        2 - $h,
                        $w - 4,
                        2 - $h,
                        0,
                        1,
                        0,
                        0,
                        0,
                        -1
                    ];
                } else if ($r == $h - 2 && $c == 0 && ($w & 7) == 4) {
                    $k = [ /* corner C layout */
                        $w - 1,
                        5 - $h,
                        $w - 1,
                        4 - $h,
                        $w - 1,
                        3 - $h,
                        $w - 1,
                        2 - $h,
                        $w - 2,
                        2 - $h,
                        0,
                        1,
                        0,
                        0,
                        0,
                        -1
                    ];
                } else if ($r == 1 && $c == $w - 1 && ($w & 7) == 0 && ($h & 7) == 6) {
                    continue; /* omit corner D */
                } else {
                    $k = $b; /* nominal L - shape layout */
                }
            }

            // Layout each bit
            for ($el_val = $enc[$i++], $j = 0; $el_val > 0; $j += 2, $el_val >>= 1) {
                if ($el_val & 1) {
                    $x = $c + $k[$j];
                    $y = $r + $k[$j + 1];

                    // Wrap around
                    if ($x < 0) {
                        $x += $w;
                        $y += 4 - (($w + 4) & 7);
                    }
                    if ($y < 0) {
                        $y += $h;
                        $x += 4 - (($h + 4) & 7);
                    }

                    // Region gap
                    $this->bit($x + 2 * intval($x / $fw) + 1, $y + 2 * intval($y / $fh) + 1);
                }
            }
        }

        // Unfilled corner
        for ($i = $w; $i & 3; $i--) {
            $this->bit($i, $i);
        }

        $this->xx = $w + 2 * $nc;
        $this->yy = $h + 2 * $nr;
    }

    /**
     * Check if a color is a valid hex color
     * @param string $c Color string
     * @return bool True if valid hex color
     */
    private function isHex($c)
    {
        return preg_match('/^#[0-9a-f]{3}(?:[0-9a-f]{3})?$/i', $c) === 1;
    }

    /**
     * Generate SVG representation of the datamatrix
     * @param array $options Optional additional options to override instance configuration
     * @return string SVG string
     */
    public function getSVG($options = [])
    {
        $q = array_merge($this->q, $options);
        $p = $q['pal'] ?? ['#000'];
        $dm = abs($q['dim'] ?? 0) ?: 256;
        // Use padding from options if provided, otherwise from q config or default to 2
        $pd = isset($q['pad']) ? abs($q['pad']) : 2;
        $mx = [1, 0, 0, 1, $pd, $pd];

        $fg = $p[0];
        $fg = $this->isHex($fg) ? $fg : '#000';

        // 处理背景色,支持透明背景
        $bg = isset($p[1]) ? $p[1] : '';
        // 如果背景色是'transparent'或者不是有效的十六进制颜色,则设置为透明(不添加背景矩形)
        $transparentBg = $bg === 'transparent' || !$this->isHex($bg);
        $bgColor = $this->isHex($bg) ? $bg : '';

        // Render optimized or verbose svg
        $optimized = empty($q['vrb']) ? 1 : 0;

        $sx = $this->xx + $pd * 2;
        $sy = $this->yy + $pd * 2;

        $path = '';
        $y = $this->yy;

        while ($y--) {
            $d = 0;
            $x = $this->xx;

            while ($x--) {
                if (isset($this->M[$y][$x]) && $this->M[$y][$x]) {
                    if ($optimized) {
                        $d++;

                        if (!isset($this->M[$y][$x - 1]) || !$this->M[$y][$x - 1]) {
                            $path .= 'M' . $x . ',' . $y . 'h' . $d . 'v1h-' . $d . 'v-1z';
                            $d = 0;
                        }
                    } else {
                        $path .= 'M' . $x . ',' . $y . 'h1v1h-1v-1z';
                    }
                }
            }
        }

        $svg = '<svg viewBox="0 0 ' . $sx . ' ' . $sy . '" width="' . (int)($dm / $sy * $sx) . '" height="' . $dm . '" fill="' . $fg . '" shape-rendering="crispEdges" xmlns="http://www.w3.org/2000/svg" version="1.1">';

        // 只有当不是透明背景且有有效的背景色时才添加背景矩形
        if (!$transparentBg && $bgColor) {
            $svg .= '<path fill="' . $bgColor . '" d="M0,0v' . $sy . 'h' . $sx . 'V0H0Z"/>';
        }

        $svg .= '<path transform="matrix(' . implode(',', $mx) . ')" d="' . $path . '"/>';
        $svg .= '</svg>';

        return $svg;
    }

    /**
     * Static method to create a DataMatrix
     * @param mixed $Q String message or configuration array
     * @return string SVG string
     */
    public static function create($Q)
    {
        // Create a new instance with the configuration
        $dm = new self();
        // Explicitly encode the message with the configuration
        $dm->encodeMsg($Q);

        // If Q is an array, pass it directly to getSVG to ensure all parameters are used
        if (is_array($Q)) {
            return $dm->getSVG($Q);
        }
        return $dm->getSVG();
    }
}

// 调用示例
echo '<?xml version="1.0" encoding="UTF-8"?>';
$config = array(
    'msg' => $_GET['content'] ?? 'NULL',    // 内容
    'dim' => $_GET['size'] ?? 256,          // 尺寸(高度)
    'pad' => $_GET['padding'] ?? 2,         // 内边距
    //'pal' => array('#000000', '#ffffff'), // 颜色调色板(前景色、背景色)
    'rct' => $_GET['rect'] ?? 0       // 是否使用矩形格式(默认正方形)
);

$color = $_GET['color'] ?? '';
if (!empty($color)){
    $color = explode(',', $color);
    $color = array_map(function($c) { return '#' . $c; }, $color);
    $config['pal'] = $color;
}

$svg_custom = DATAMatrix::create($config);
echo $svg_custom;