Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
56.74% covered (warning)
56.74%
299 / 527
17.65% covered (danger)
17.65%
3 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
Datamatrix
56.74% covered (warning)
56.74%
299 / 527
17.65% covered (danger)
17.65%
3 / 17
3274.23
0.00% covered (danger)
0.00%
0 / 1
 generateCodeArray
78.85% covered (warning)
78.85%
41 / 52
0.00% covered (danger)
0.00%
0 / 1
28.01
 getGFProduct
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 getErrorCorrection
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
10
 get253StateCodeword
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get255StateCodeword
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isCharMode
77.78% covered (warning)
77.78%
7 / 9
0.00% covered (danger)
0.00%
0 / 1
33.42
 lookAheadTest
72.73% covered (warning)
72.73%
104 / 143
0.00% covered (danger)
0.00%
0 / 1
62.29
 getSwitchEncodingCodeword
33.33% covered (danger)
33.33%
3 / 9
0.00% covered (danger)
0.00%
0 / 1
26.96
 getMaxDataCodewords
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 getHighLevelEncoding
34.81% covered (danger)
34.81%
63 / 181
0.00% covered (danger)
0.00%
0 / 1
801.23
 placeModule
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 placeUtah
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 placeCornerA
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 placeCornerB
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 placeCornerC
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 placeCornerD
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getPlacementMap
72.97% covered (warning)
72.97%
27 / 37
0.00% covered (danger)
0.00%
0 / 1
31.56
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\Utils\Barcode
8 * @author    Nicola Asuni - Tecnick.com LTD - www.tecnick.com <info@tecnick.com>
9 * @copyright Copyright (C) 2010 - 2014  Nicola Asuni - Tecnick.com LTD
10 * @license   GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
11 * @version   1.0.0
12 * @link      https://jingga.app
13 */
14declare(strict_types=1);
15
16namespace phpOMS\Utils\Barcode;
17
18/**
19 * Datamatrix class.
20 *
21 * @package phpOMS\Utils\Barcode
22 * @license GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html)
23 * @link    https://jingga.app
24 * @since   1.0.0
25 */
26class Datamatrix extends TwoDAbstract
27{
28    private const ENC_ASCII = 0;
29
30    private const ENC_C40 = 1;
31
32    private const ENC_TXT = 2;
33
34    private const ENC_X12 = 3;
35
36    private const ENC_EDF = 4;
37
38    private const ENC_BASE256 = 5;
39
40    private const ENC_ASCII_EXT = 6;
41
42    private const ENC_ASCII_NUM = 7;
43
44    private const ECC_200_SYMBOL_ATTR = [
45        // square form ---------------------------------------------------------------------------------------
46        [0x00a, 0x00a, 0x008, 0x008, 0x00a, 0x00a, 0x008, 0x008, 0x001, 0x001, 0x001, 0x003, 0x005, 0x001, 0x003, 0x005], // 10x10
47        [0x00c, 0x00c, 0x00a, 0x00a, 0x00c, 0x00c, 0x00a, 0x00a, 0x001, 0x001, 0x001, 0x005, 0x007, 0x001, 0x005, 0x007], // 12x12
48        [0x00e, 0x00e, 0x00c, 0x00c, 0x00e, 0x00e, 0x00c, 0x00c, 0x001, 0x001, 0x001, 0x008, 0x00a, 0x001, 0x008, 0x00a], // 14x14
49        [0x010, 0x010, 0x00e, 0x00e, 0x010, 0x010, 0x00e, 0x00e, 0x001, 0x001, 0x001, 0x00c, 0x00c, 0x001, 0x00c, 0x00c], // 16x16
50        [0x012, 0x012, 0x010, 0x010, 0x012, 0x012, 0x010, 0x010, 0x001, 0x001, 0x001, 0x012, 0x00e, 0x001, 0x012, 0x00e], // 18x18
51        [0x014, 0x014, 0x012, 0x012, 0x014, 0x014, 0x012, 0x012, 0x001, 0x001, 0x001, 0x016, 0x012, 0x001, 0x016, 0x012], // 20x20
52        [0x016, 0x016, 0x014, 0x014, 0x016, 0x016, 0x014, 0x014, 0x001, 0x001, 0x001, 0x01e, 0x014, 0x001, 0x01e, 0x014], // 22x22
53        [0x018, 0x018, 0x016, 0x016, 0x018, 0x018, 0x016, 0x016, 0x001, 0x001, 0x001, 0x024, 0x018, 0x001, 0x024, 0x018], // 24x24
54        [0x01a, 0x01a, 0x018, 0x018, 0x01a, 0x01a, 0x018, 0x018, 0x001, 0x001, 0x001, 0x02c, 0x01c, 0x001, 0x02c, 0x01c], // 26x26
55        [0x020, 0x020, 0x01c, 0x01c, 0x010, 0x010, 0x00e, 0x00e, 0x002, 0x002, 0x004, 0x03e, 0x024, 0x001, 0x03e, 0x024], // 32x32
56        [0x024, 0x024, 0x020, 0x020, 0x012, 0x012, 0x010, 0x010, 0x002, 0x002, 0x004, 0x056, 0x02a, 0x001, 0x056, 0x02a], // 36x36
57        [0x028, 0x028, 0x024, 0x024, 0x014, 0x014, 0x012, 0x012, 0x002, 0x002, 0x004, 0x072, 0x030, 0x001, 0x072, 0x030], // 40x40
58        [0x02c, 0x02c, 0x028, 0x028, 0x016, 0x016, 0x014, 0x014, 0x002, 0x002, 0x004, 0x090, 0x038, 0x001, 0x090, 0x038], // 44x44
59        [0x030, 0x030, 0x02c, 0x02c, 0x018, 0x018, 0x016, 0x016, 0x002, 0x002, 0x004, 0x0ae, 0x044, 0x001, 0x0ae, 0x044], // 48x48
60        [0x034, 0x034, 0x030, 0x030, 0x01a, 0x01a, 0x018, 0x018, 0x002, 0x002, 0x004, 0x0cc, 0x054, 0x002, 0x066, 0x02a], // 52x52
61        [0x040, 0x040, 0x038, 0x038, 0x010, 0x010, 0x00e, 0x00e, 0x004, 0x004, 0x010, 0x118, 0x070, 0x002, 0x08c, 0x038], // 64x64
62        [0x048, 0x048, 0x040, 0x040, 0x012, 0x012, 0x010, 0x010, 0x004, 0x004, 0x010, 0x170, 0x090, 0x004, 0x05c, 0x024], // 72x72
63        [0x050, 0x050, 0x048, 0x048, 0x014, 0x014, 0x012, 0x012, 0x004, 0x004, 0x010, 0x1c8, 0x0c0, 0x004, 0x072, 0x030], // 80x80
64        [0x058, 0x058, 0x050, 0x050, 0x016, 0x016, 0x014, 0x014, 0x004, 0x004, 0x010, 0x240, 0x0e0, 0x004, 0x090, 0x038], // 88x88
65        [0x060, 0x060, 0x058, 0x058, 0x018, 0x018, 0x016, 0x016, 0x004, 0x004, 0x010, 0x2b8, 0x110, 0x004, 0x0ae, 0x044], // 96x96
66        [0x068, 0x068, 0x060, 0x060, 0x01a, 0x01a, 0x018, 0x018, 0x004, 0x004, 0x010, 0x330, 0x150, 0x006, 0x088, 0x038], // 104x104
67        [0x078, 0x078, 0x06c, 0x06c, 0x014, 0x014, 0x012, 0x012, 0x006, 0x006, 0x024, 0x41a, 0x198, 0x006, 0x0af, 0x044], // 120x120
68        [0x084, 0x084, 0x078, 0x078, 0x016, 0x016, 0x014, 0x014, 0x006, 0x006, 0x024, 0x518, 0x1f0, 0x008, 0x0a3, 0x03e], // 132x132
69        [0x090, 0x090, 0x084, 0x084, 0x018, 0x018, 0x016, 0x016, 0x006, 0x006, 0x024, 0x616, 0x26c, 0x00a, 0x09c, 0x03e], // 144x144
70        // rectangular form (currently unused) ---------------------------------------------------------------------------
71        [0x008, 0x012, 0x006, 0x010, 0x008, 0x012, 0x006, 0x010, 0x001, 0x001, 0x001, 0x005, 0x007, 0x001, 0x005, 0x007], // 8x18
72        [0x008, 0x020, 0x006, 0x01c, 0x008, 0x010, 0x006, 0x00e, 0x001, 0x002, 0x002, 0x00a, 0x00b, 0x001, 0x00a, 0x00b], // 8x32
73        [0x00c, 0x01a, 0x00a, 0x018, 0x00c, 0x01a, 0x00a, 0x018, 0x001, 0x001, 0x001, 0x010, 0x00e, 0x001, 0x010, 0x00e], // 12x26
74        [0x00c, 0x024, 0x00a, 0x020, 0x00c, 0x012, 0x00a, 0x010, 0x001, 0x002, 0x002, 0x00c, 0x012, 0x001, 0x00c, 0x012], // 12x36
75        [0x010, 0x024, 0x00e, 0x020, 0x010, 0x012, 0x00e, 0x010, 0x001, 0x002, 0x002, 0x020, 0x018, 0x001, 0x020, 0x018], // 16x36
76        [0x010, 0x030, 0x00e, 0x02c, 0x010, 0x018, 0x00e, 0x016, 0x001, 0x002, 0x002, 0x031, 0x01c, 0x001, 0x031, 0x01c],  // 16x48
77    ];
78
79    private const CHARSET = [
80        self::ENC_C40 => [ // Basic set for C40 ----------------------------------------------------------------------------
81            'S1' => 0x00, 'S2' => 0x01, 'S3' => 0x02, 0x20 => 0x03, 0x30 => 0x04, 0x31 => 0x05, 0x32 => 0x06, 0x33 => 0x07, 0x34 => 0x08, 0x35 => 0x09,
82            0x36 => 0x0a, 0x37 => 0x0b, 0x38 => 0x0c, 0x39 => 0x0d, 0x41 => 0x0e, 0x42 => 0x0f, 0x43 => 0x10, 0x44 => 0x11, 0x45 => 0x12, 0x46 => 0x13,
83            0x47 => 0x14, 0x48 => 0x15, 0x49 => 0x16, 0x4a => 0x17, 0x4b => 0x18, 0x4c => 0x19, 0x4d => 0x1a, 0x4e => 0x1b, 0x4f => 0x1c, 0x50 => 0x1d,
84            0x51 => 0x1e, 0x52 => 0x1f, 0x53 => 0x20, 0x54 => 0x21, 0x55 => 0x22, 0x56 => 0x23, 0x57 => 0x24, 0x58 => 0x25, 0x59 => 0x26, 0x5a => 0x27,
85        ],
86        self::ENC_TXT => [ // Basic set for TEXT ---------------------------------------------------------------------------
87            'S1' => 0x00, 'S2' => 0x01, 'S3' => 0x02, 0x20 => 0x03, 0x30 => 0x04, 0x31 => 0x05, 0x32 => 0x06, 0x33 => 0x07, 0x34 => 0x08, 0x35 => 0x09,
88            0x36 => 0x0a, 0x37 => 0x0b, 0x38 => 0x0c, 0x39 => 0x0d, 0x61 => 0x0e, 0x62 => 0x0f, 0x63 => 0x10, 0x64 => 0x11, 0x65 => 0x12, 0x66 => 0x13,
89            0x67 => 0x14, 0x68 => 0x15, 0x69 => 0x16, 0x6a => 0x17, 0x6b => 0x18, 0x6c => 0x19, 0x6d => 0x1a, 0x6e => 0x1b, 0x6f => 0x1c, 0x70 => 0x1d,
90            0x71 => 0x1e, 0x72 => 0x1f, 0x73 => 0x20, 0x74 => 0x21, 0x75 => 0x22, 0x76 => 0x23, 0x77 => 0x24, 0x78 => 0x25, 0x79 => 0x26, 0x7a => 0x27,
91        ],
92        'SH1' => [ // Shift 1 set ----------------------------------------------------------------------------------
93            0x00 => 0x00, 0x01 => 0x01, 0x02 => 0x02, 0x03 => 0x03, 0x04 => 0x04, 0x05 => 0x05, 0x06 => 0x06, 0x07 => 0x07, 0x08 => 0x08, 0x09 => 0x09,
94            0x0a => 0x0a, 0x0b => 0x0b, 0x0c => 0x0c, 0x0d => 0x0d, 0x0e => 0x0e, 0x0f => 0x0f, 0x10 => 0x10, 0x11 => 0x11, 0x12 => 0x12, 0x13 => 0x13,
95            0x14 => 0x14, 0x15 => 0x15, 0x16 => 0x16, 0x17 => 0x17, 0x18 => 0x18, 0x19 => 0x19, 0x1a => 0x1a, 0x1b => 0x1b, 0x1c => 0x1c, 0x1d => 0x1d,
96            0x1e => 0x1e, 0x1f => 0x1f,
97        ],
98        'SH2' => [ // Shift 2 set ----------------------------------------------------------------------------------
99            0x21 => 0x00, 0x22 => 0x01, 0x23 => 0x02, 0x24 => 0x03, 0x25 => 0x04, 0x26 => 0x05, 0x27 => 0x06, 0x28 => 0x07, 0x29 => 0x08, 0x2a => 0x09,
100            0x2b => 0x0a, 0x2c => 0x0b, 0x2d => 0x0c, 0x2e => 0x0d, 0x2f => 0x0e, 0x3a => 0x0f, 0x3b => 0x10, 0x3c => 0x11, 0x3d => 0x12, 0x3e => 0x13,
101            0x3f => 0x14, 0x40 => 0x15, 0x5b => 0x16, 0x5c => 0x17, 0x5d => 0x18, 0x5e => 0x19, 0x5f => 0x1a, 'F1' => 0x1b, 'US' => 0x1e,
102        ],
103        'S3C' => [ // Shift 3 set for C40 --------------------------------------------------------------------------
104            0x60 => 0x00, 0x61 => 0x01, 0x62 => 0x02, 0x63 => 0x03, 0x64 => 0x04, 0x65 => 0x05, 0x66 => 0x06, 0x67 => 0x07, 0x68 => 0x08, 0x69 => 0x09,
105            0x6a => 0x0a, 0x6b => 0x0b, 0x6c => 0x0c, 0x6d => 0x0d, 0x6e => 0x0e, 0x6f => 0x0f, 0x70 => 0x10, 0x71 => 0x11, 0x72 => 0x12, 0x73 => 0x13,
106            0x74 => 0x14, 0x75 => 0x15, 0x76 => 0x16, 0x77 => 0x17, 0x78 => 0x18, 0x79 => 0x19, 0x7a => 0x1a, 0x7b => 0x1b, 0x7c => 0x1c, 0x7d => 0x1d,
107            0x7e => 0x1e, 0x7f => 0x1f,
108        ],
109        'S3T' => [ // Shift 3 set for TEXT -------------------------------------------------------------------------
110            0x60 => 0x00, 0x41 => 0x01, 0x42 => 0x02, 0x43 => 0x03, 0x44 => 0x04, 0x45 => 0x05, 0x46 => 0x06, 0x47 => 0x07, 0x48 => 0x08, 0x49 => 0x09,
111            0x4a => 0x0a, 0x4b => 0x0b, 0x4c => 0x0c, 0x4d => 0x0d, 0x4e => 0x0e, 0x4f => 0x0f, 0x50 => 0x10, 0x51 => 0x11, 0x52 => 0x12, 0x53 => 0x13,
112            0x54 => 0x14, 0x55 => 0x15, 0x56 => 0x16, 0x57 => 0x17, 0x58 => 0x18, 0x59 => 0x19, 0x5a => 0x1a, 0x7b => 0x1b, 0x7c => 0x1c, 0x7d => 0x1d,
113            0x7e => 0x1e, 0x7f => 0x1f,
114        ],
115        self::ENC_X12 => [ // Set for X12 ----------------------------------------------------------------------------------
116            0x0d => 0x00, 0x2a => 0x01, 0x3e => 0x02, 0x20 => 0x03, 0x30 => 0x04, 0x31 => 0x05, 0x32 => 0x06, 0x33 => 0x07, 0x34 => 0x08, 0x35 => 0x09,
117            0x36 => 0x0a, 0x37 => 0x0b, 0x38 => 0x0c, 0x39 => 0x0d, 0x41 => 0x0e, 0x42 => 0x0f, 0x43 => 0x10, 0x44 => 0x11, 0x45 => 0x12, 0x46 => 0x13,
118            0x47 => 0x14, 0x48 => 0x15, 0x49 => 0x16, 0x4a => 0x17, 0x4b => 0x18, 0x4c => 0x19, 0x4d => 0x1a, 0x4e => 0x1b, 0x4f => 0x1c, 0x50 => 0x1d,
119            0x51 => 0x1e, 0x52 => 0x1f, 0x53 => 0x20, 0x54 => 0x21, 0x55 => 0x22, 0x56 => 0x23, 0x57 => 0x24, 0x58 => 0x25, 0x59 => 0x26, 0x5a => 0x27,
120        ],
121    ];
122
123    public int $encoding = self::ENC_ASCII;
124
125    /**
126     * {@inheritdoc}
127     */
128    public function generateCodeArray() : array
129    {
130        $this->codearray = [];
131
132        // get data codewords
133        $cw = $this->getHighLevelEncoding($this->content);
134
135        // number of data codewords
136        $nd = \count($cw);
137
138        // check size
139        if ($nd > 1558) {
140            return [];
141        }
142
143        // get minimum required matrix size.
144        foreach (self::ECC_200_SYMBOL_ATTR as $params) {
145            if ($params[11] >= $nd) {
146                break;
147            }
148        }
149
150        if ($params[11] < $nd) {
151            // too much data
152            return [];
153        } elseif ($params[11] > $nd) {
154            // add padding
155            if ((($params[11] - $nd) > 1) && ($cw[($nd - 1)] !== 254)) {
156                if ($this->encoding === self::ENC_EDF) {
157                    // switch to ASCII encoding
158                    $cw[] = 124;
159                    ++$nd;
160                } elseif (($this->encoding !== self::ENC_ASCII) && ($this->encoding !== self::ENC_BASE256)) {
161                    // switch to ASCII encoding
162                    $cw[] = 254;
163                    ++$nd;
164                }
165            }
166
167            if ($params[11] > $nd) {
168                // add first pad
169                $cw[] = 129;
170                ++$nd;
171
172                // add remaining pads
173                for ($i = $nd; $i < $params[11]; ++$i) {
174                    $cw[] = $this->get253StateCodeword(129, $i);
175                }
176            }
177        }
178
179        // add error correction codewords
180        $cw = $this->getErrorCorrection($cw, $params[13], $params[14], $params[15]);
181
182        // initialize empty arrays
183        //$this->codearray = \array_fill(0, ($params[2] * $params[3]), 0);
184        for ($i = 0; $i < $params[2]; ++$i) {
185            $this->codearray[$i] = \array_fill(0, $params[3], false);
186        }
187
188        // get placement map
189        $places = $this->getPlacementMap($params[2], $params[3]);
190
191        // fill the grid with data
192        $i = 0;
193
194        // region data row max index
195        $rdri = ($params[4] - 1);
196
197        // region data column max index
198        $rdci = ($params[5] - 1);
199
200        // for each vertical region
201        for ($vr = 0; $vr < $params[9]; ++$vr) {
202            // for each row on region
203            for ($r = 0; $r < $params[4]; ++$r) {
204                // get row
205                $row = (($vr * $params[4]) + $r);
206
207                // for each horizontal region
208                for ($hr = 0; $hr < $params[8]; ++$hr) {
209                    // for each column on region
210                    for ($c = 0; $c < $params[5]; ++$c) {
211                        // get column
212                        $col = (($hr * $params[5]) + $c);
213
214                        // braw bits by case
215                        if ($r === 0) {
216                            // top finder pattern
217                            $this->codearray[$row][$col] = ($c % 2) === 0;
218                        } elseif ($r === $rdri) {
219                            // bottom finder pattern
220                            $this->codearray[$row][$col] = true;
221                        } elseif ($c === 0) {
222                            // left finder pattern
223                            $this->codearray[$row][$col] = true;
224                        } elseif ($c === $rdci) {
225                            // right finder pattern
226                            $this->codearray[$row][$col] = ($r % 2) === 0;
227                        } elseif ($places[$i] < 2) { // data bit
228                            $this->codearray[$row][$col] = (bool) $places[$i];
229
230                            ++$i;
231                        } else {
232                            // codeword ID
233                            $cw_id = (int) (\floor($places[$i] / 10) - 1);
234                            // codeword BIT mask
235                            $cw_bit                      = \pow(2, (8 - ($places[$i] % 10)));
236                            $this->codearray[$row][$col] = ($cw[$cw_id] & $cw_bit) !== 0;
237
238                            ++$i;
239                        }
240                    }
241                }
242            }
243        }
244
245        return $this->codearray;
246    }
247
248    /**
249     * Product of two numbers in a Power-of-Two Galois Field
250     */
251    protected function getGFProduct(int $a, int $b, array $log, array $alog, int $gf) : int
252    {
253        if (($a === 0) || ($b === 0)) {
254            return 0;
255        }
256
257        return $alog[($log[$a] + $log[$b]) % ($gf - 1)];
258    }
259
260    /**
261     * Add error correction codewords to data codewords array (ANNEX E).
262     */
263    protected function getErrorCorrection(array $wd, int $nb, int $nd, int $nc, int $gf = 256, int $pp = 301) : array
264    {
265        // generate the log ($log) and antilog ($alog) tables
266        $log[0]  = 0;
267        $alog[0] = 1;
268
269        for ($i = 1; $i < $gf; ++$i) {
270            $alog[$i] = ($alog[($i - 1)] * 2);
271
272            if ($alog[$i] >= $gf) {
273                $alog[$i] ^= $pp;
274            }
275
276            $log[$alog[$i]] = $i;
277        }
278
279        \ksort($log);
280
281        // generate the polynomial coefficients (c)
282        $c    = \array_fill(0, ($nc + 1), 0);
283        $c[0] = 1;
284
285        for ($i = 1; $i <= $nc; ++$i) {
286            $c[$i] = $c[($i - 1)];
287
288            for ($j = ($i - 1); $j >= 1; --$j) {
289                $c[$j] = $c[($j - 1)] ^ $this->getGFProduct($c[$j], $alog[$i], $log, $alog, $gf);
290            }
291
292            $c[0] = $this->getGFProduct($c[0], $alog[$i], $log, $alog, $gf);
293        }
294
295        \ksort($c);
296
297        // total number of data codewords
298        $num_wd = ($nb * $nd);
299
300        // total number of error codewords
301        $num_we = ($nb * $nc);
302
303        // for each block
304        for ($b = 0; $b < $nb; ++$b) {
305            // create interleaved data block
306            $block = [];
307            for ($n = $b; $n < $num_wd; $n += $nb) {
308                $block[] = $wd[$n];
309            }
310
311            // initialize error codewords
312            $we = \array_fill(0, ($nc + 1), 0);
313
314            // calculate error correction codewords for this block
315            for ($i = 0; $i < $nd; ++$i) {
316                $k = ($we[0] ^ $block[$i]);
317
318                for ($j = 0; $j < $nc; ++$j) {
319                    $we[$j] = ($we[($j + 1)] ^ $this->getGFProduct($k, $c[($nc - $j - 1)], $log, $alog, $gf));
320                }
321            }
322
323            // add error codewords at the end of data codewords
324            $j = 0;
325            for ($i = $b; $i < $num_we; $i += $nb) {
326                $wd[($num_wd + $i)] = $we[$j];
327                ++$j;
328            }
329        }
330
331        // reorder codewords
332        \ksort($wd);
333
334        return $wd;
335    }
336
337    /**
338     * Return the 253-state codeword
339     */
340    protected function get253StateCodeword(int $cwpad, int $cwpos) : int
341    {
342        $pad = ($cwpad + (((149 * $cwpos) % 253) + 1));
343
344        if ($pad > 254) {
345            $pad -= 254;
346        }
347
348        return $pad;
349    }
350
351    /**
352     * Return the 255-state codeword
353     */
354    protected function get255StateCodeword(int $cwpad, int $cwpos) : int
355    {
356        $pad = ($cwpad + (((149 * $cwpos) % 255) + 1));
357
358        if ($pad > 255) {
359            $pad -= 256;
360        }
361
362        return $pad;
363    }
364
365    /**
366     * Returns true if the char belongs to the selected mode
367     */
368    protected function isCharMode(int $chr, int $mode) : bool
369    {
370        switch ($mode) {
371            case self::ENC_ASCII:
372                // ASCII character 0 to 127
373                return (($chr >= 0) && ($chr <= 127));
374            case self::ENC_C40:
375                // Upper-case alphanumeric
376                return (($chr === 32) || (($chr >= 48) && ($chr <= 57)) || (($chr >= 65) && ($chr <= 90)));
377            case self::ENC_TXT:
378                // Lower-case alphanumeric
379                return (($chr === 32) || (($chr >= 48) && ($chr <= 57)) || (($chr >= 97) && ($chr <= 122)));
380            case self::ENC_X12:
381                // ANSI X12
382                return (($chr === 13) || ($chr === 42) || ($chr === 62));
383            case self::ENC_EDF:
384                // ASCII character 32 to 94
385                return (($chr >= 32) && ($chr <= 94));
386            case self::ENC_BASE256:
387                // Function character (FNC1, Structured Append, Reader Program, or Code Page)
388                return (($chr === 232) || ($chr === 233) || ($chr === 234) || ($chr === 241));
389            case self::ENC_ASCII_EXT:
390                // ASCII character 128 to 255
391                return (($chr >= 128) && ($chr <= 255));
392            case self::ENC_ASCII_NUM:
393                // ASCII digits
394                return (($chr >= 48) && ($chr <= 57));
395        }
396
397        return false;
398    }
399
400    /**
401     * The look-ahead test scans the data to be encoded to find the best mode (Annex P - steps from J to S).
402     */
403    protected function lookAheadTest(string $data, int $pos, int $mode) : int
404    {
405        $data_length = \strlen($data);
406
407        if ($pos >= $data_length) {
408            return $mode;
409        }
410
411        $charscount = 0; // \count processed chars
412
413        // STEP J
414        if ($mode === self::ENC_ASCII) {
415            $numch = [0, 1, 1, 1, 1, 1.25];
416        } else {
417            $numch        = [1, 2, 2, 2, 2, 2.25];
418            $numch[$mode] = 0;
419        }
420
421        while (true) {
422            // STEP K
423            if (($pos + $charscount) === $data_length) {
424                if ($numch[self::ENC_ASCII] <= \ceil(\min(
425                        $numch[self::ENC_C40],
426                        $numch[self::ENC_TXT],
427                        $numch[self::ENC_X12],
428                        $numch[self::ENC_EDF],
429                        $numch[self::ENC_BASE256]
430                    ))
431                ) {
432                    return self::ENC_ASCII;
433                }
434
435                if ($numch[self::ENC_BASE256] < \ceil(\min(
436                        $numch[self::ENC_ASCII],
437                        $numch[self::ENC_C40],
438                        $numch[self::ENC_TXT],
439                        $numch[self::ENC_X12],
440                        $numch[self::ENC_EDF]
441                    ))
442                ) {
443                    return self::ENC_BASE256;
444                }
445
446                if ($numch[self::ENC_EDF] < \ceil(\min(
447                        $numch[self::ENC_ASCII],
448                        $numch[self::ENC_C40],
449                        $numch[self::ENC_TXT],
450                        $numch[self::ENC_X12],
451                        $numch[self::ENC_BASE256]
452                    ))
453                ) {
454                    return self::ENC_EDF;
455                }
456
457                if ($numch[self::ENC_TXT] < \ceil(\min(
458                        $numch[self::ENC_ASCII],
459                        $numch[self::ENC_C40],
460                        $numch[self::ENC_X12],
461                        $numch[self::ENC_EDF],
462                        $numch[self::ENC_BASE256]
463                    ))
464                ) {
465                    return self::ENC_TXT;
466                }
467
468                if ($numch[self::ENC_X12] < \ceil(\min(
469                        $numch[self::ENC_ASCII],
470                        $numch[self::ENC_C40],
471                        $numch[self::ENC_TXT],
472                        $numch[self::ENC_EDF],
473                        $numch[self::ENC_BASE256]
474                    ))
475                ) {
476                    return self::ENC_X12;
477                }
478
479                return self::ENC_C40;
480            }
481
482            // get char
483            $chr = \ord($data[$pos + $charscount]);
484            ++$charscount;
485
486            // STEP L
487            if ($this->isCharMode($chr, self::ENC_ASCII_NUM)) {
488                $numch[self::ENC_ASCII] += (1 / 2);
489            } elseif ($this->isCharMode($chr, self::ENC_ASCII_EXT)) {
490                $numch[self::ENC_ASCII]  = \ceil($numch[self::ENC_ASCII]);
491                $numch[self::ENC_ASCII] += 2;
492            } else {
493                $numch[self::ENC_ASCII] = \ceil($numch[self::ENC_ASCII]);
494                ++$numch[self::ENC_ASCII];
495            }
496
497            // STEP M
498            if ($this->isCharMode($chr, self::ENC_C40)) {
499                $numch[self::ENC_C40] += (2 / 3);
500            } elseif ($this->isCharMode($chr, self::ENC_ASCII_EXT)) {
501                $numch[self::ENC_C40] += (8 / 3);
502            } else {
503                $numch[self::ENC_C40] += (4 / 3);
504            }
505
506            // STEP N
507            if ($this->isCharMode($chr, self::ENC_TXT)) {
508                $numch[self::ENC_TXT] += (2 / 3);
509            } elseif ($this->isCharMode($chr, self::ENC_ASCII_EXT)) {
510                $numch[self::ENC_TXT] += (8 / 3);
511            } else {
512                $numch[self::ENC_TXT] += (4 / 3);
513            }
514
515            // STEP O
516            if ($this->isCharMode($chr, self::ENC_X12) || $this->isCharMode($chr, self::ENC_C40)) {
517                $numch[self::ENC_X12] += (2 / 3);
518            } elseif ($this->isCharMode($chr, self::ENC_ASCII_EXT)) {
519                $numch[self::ENC_X12] += (13 / 3);
520            } else {
521                $numch[self::ENC_X12] += (10 / 3);
522            }
523
524            // STEP P
525            if ($this->isCharMode($chr, self::ENC_EDF)) {
526                $numch[self::ENC_EDF] += (3 / 4);
527            } elseif ($this->isCharMode($chr, self::ENC_ASCII_EXT)) {
528                $numch[self::ENC_EDF] += (17 / 4);
529            } else {
530                $numch[self::ENC_EDF] += (13 / 4);
531            }
532
533            // STEP Q
534            if ($this->isCharMode($chr, self::ENC_BASE256)) {
535                $numch[self::ENC_BASE256] += 4;
536            } else {
537                ++$numch[self::ENC_BASE256];
538            }
539
540            // STEP R
541            if ($charscount >= 4) {
542                if (($numch[self::ENC_ASCII] + 1) <= \min(
543                        $numch[self::ENC_C40],
544                        $numch[self::ENC_TXT],
545                        $numch[self::ENC_X12],
546                        $numch[self::ENC_EDF],
547                        $numch[self::ENC_BASE256]
548                    )
549                ) {
550                    return self::ENC_ASCII;
551                }
552
553                if ((($numch[self::ENC_BASE256] + 1) <= $numch[self::ENC_ASCII])
554                    || ($numch[self::ENC_BASE256] + 1) < \min(
555                            $numch[self::ENC_C40],
556                            $numch[self::ENC_TXT],
557                            $numch[self::ENC_X12],
558                            $numch[self::ENC_EDF]
559                        )
560                ) {
561                    return self::ENC_BASE256;
562                }
563
564                if (($numch[self::ENC_EDF] + 1) < \min(
565                        $numch[self::ENC_ASCII],
566                        $numch[self::ENC_C40],
567                        $numch[self::ENC_TXT],
568                        $numch[self::ENC_X12],
569                        $numch[self::ENC_BASE256]
570                    )
571                ) {
572                    return self::ENC_EDF;
573                }
574
575                if (($numch[self::ENC_TXT] + 1) < \min(
576                        $numch[self::ENC_ASCII],
577                        $numch[self::ENC_C40],
578                        $numch[self::ENC_X12],
579                        $numch[self::ENC_EDF],
580                        $numch[self::ENC_BASE256]
581                    )
582                ) {
583                    return self::ENC_TXT;
584                }
585
586                if (($numch[self::ENC_X12] + 1) < \min(
587                        $numch[self::ENC_ASCII],
588                        $numch[self::ENC_C40],
589                        $numch[self::ENC_TXT],
590                        $numch[self::ENC_EDF],
591                        $numch[self::ENC_BASE256]
592                    )
593                ) {
594                    return self::ENC_X12;
595                }
596
597                if (($numch[self::ENC_C40] + 1) < \min(
598                        $numch[self::ENC_ASCII],
599                        $numch[self::ENC_TXT],
600                        $numch[self::ENC_EDF],
601                        $numch[self::ENC_BASE256]
602                    )
603                ) {
604                    if ($numch[self::ENC_C40] < $numch[self::ENC_X12]) {
605                        return self::ENC_C40;
606                    }
607
608                    if ($numch[self::ENC_C40] === $numch[self::ENC_X12]) {
609                        $k = ($pos + $charscount + 1);
610
611                        while ($k < $data_length) {
612                            $tmpchr = \ord($data[$k]);
613
614                            if ($this->isCharMode($tmpchr, self::ENC_X12)) {
615                                return self::ENC_X12;
616                            } elseif (!$this->isCharMode($tmpchr, self::ENC_X12)
617                                && !$this->isCharMode($tmpchr, self::ENC_C40)
618                            ) {
619                                break;
620                            }
621
622                            ++$k;
623                        }
624
625                        return self::ENC_C40;
626                    }
627                }
628            }
629        }
630    }
631
632    /**
633     * Get the switching codeword to a new encoding mode (latch codeword)
634     */
635    protected function getSwitchEncodingCodeword(int $mode) : int
636    {
637        switch ($mode) {
638            case self::ENC_ASCII:
639                if ($this->encoding === self::ENC_EDF) {
640                    return 124;
641                }
642
643                return 254;
644            case self::ENC_C40:
645                return 230;
646            case self::ENC_TXT:
647                return 239;
648            case self::ENC_X12:
649                return 238;
650            case self::ENC_EDF:
651                return 240;
652            case self::ENC_BASE256:
653                return 231;
654        }
655
656        return 254;
657    }
658
659    /**
660     * Choose the minimum matrix size and return the max number of data codewords.
661     */
662    protected function getMaxDataCodewords(int $numcw) : int
663    {
664        foreach (self::ECC_200_SYMBOL_ATTR as $matrix) {
665            if ($matrix[11] >= $numcw) {
666                return $matrix[11];
667            }
668        }
669
670        return 0;
671    }
672
673    /**
674     * Get high level encoding using the minimum symbol data characters for ECC 200
675     */
676    protected function getHighLevelEncoding(string $data) : array
677    {
678        // STEP A. Start in ASCII encodation.
679        $enc         = self::ENC_ASCII; // current encoding mode
680        $pos         = 0; // current position
681        $cw          = []; // array of codewords to be returned
682        $cw_num      = 0; // number of data codewords
683        $data_length = \strlen($data); // number of chars
684
685        while ($pos < $data_length) {
686            $this->encoding = $enc;
687
688            switch ($enc) {
689                case self::ENC_ASCII:
690                    // STEP B. While in ASCII encodation
691                    if ($data_length > 1 && $pos < ($data_length - 1)
692                        && ($this->isCharMode(\ord($data[$pos]), self::ENC_ASCII_NUM)
693                            && $this->isCharMode(\ord($data[$pos + 1]), self::ENC_ASCII_NUM))
694                    ) {
695                        // 1. If the next data sequence is at least 2 consecutive digits, encode the next two digits as a double digit in ASCII mode.
696                        $cw[] = ((int) \substr($data, $pos, 2) + 130);
697                        ++$cw_num;
698                        $pos += 2;
699                    } else {
700                        // 2. If the look-ahead test (starting at step J) indicates another mode, switch to that mode.
701                        $newenc = $this->lookAheadTest($data, $pos, $enc);
702
703                        if ($newenc !== $enc) {
704                            $enc  = $newenc;
705                            $cw[] = $this->getSwitchEncodingCodeword($enc);
706                            ++$cw_num;
707                        } else {
708                            // get new byte
709                            $chr = \ord($data[$pos]);
710                            ++$pos;
711
712                            if ($this->isCharMode($chr, self::ENC_ASCII_EXT)) {
713                                // 3. If the next data character is extended ASCII (greater than 127) encode it in ASCII mode first using the Upper Shift (value 235) character.
714                                $cw[]    = 235;
715                                $cw[]    = ($chr - 127);
716                                $cw_num += 2;
717                            } else {
718                                // 4. Otherwise process the next data character in ASCII encodation.
719                                $cw[] = ($chr + 1);
720                                ++$cw_num;
721                            }
722                        }
723                    }
724
725                    break;
726                case self::ENC_C40:
727                    // Upper-case alphanumeric
728                case self::ENC_TXT:
729                    // Lower-case alphanumeric
730                case self::ENC_X12:
731                    // ANSI X12
732                    $temp_cw = [];
733                    $p       = 0;
734                    $epos    = $pos;
735
736                    // get basic charset for current encoding
737                    $charset = self::CHARSET[$enc];
738
739                    do {
740                        // 2. process the next character in C40 encodation.
741                        $chr = \ord($data[$epos]);
742                        ++$epos;
743
744                        // check for extended character
745                        if (($chr & 0x80) !== 0) {
746                            if ($enc === self::ENC_X12) {
747                                return [];
748                            }
749
750                            $chr      &= 0x7f;
751                            $temp_cw[] = 1; // shift 2
752                            $temp_cw[] = 30; // upper shift
753                            $p        += 2;
754                        }
755
756                        if (isset($charset[$chr])) {
757                            $temp_cw[] = $charset[$chr];
758                            ++$p;
759                        } else {
760                            if (isset(self::CHARSET['SH1'][$chr])) {
761                                $temp_cw[] = 0; // shift 1
762                                $shiftset  = self::CHARSET['SH1'];
763                            } elseif (isset($chr, self::CHARSET['SH2'][$chr])) {
764                                $temp_cw[] = 1; // shift 2
765                                $shiftset  = self::CHARSET['SH2'];
766                            } elseif ($enc === self::ENC_C40 && isset(self::CHARSET['S3C'][$chr])) {
767                                $temp_cw[] = 2; // shift 3
768                                $shiftset  = self::CHARSET['S3C'];
769                            } elseif ($enc === self::ENC_TXT && isset(self::CHARSET['S3T'][$chr])) {
770                                $temp_cw[] = 2; // shift 3
771                                $shiftset  = self::CHARSET['S3T'];
772                            } else {
773                                return [];
774                            }
775
776                            $temp_cw[] = $shiftset[$chr];
777                            $p        += 2;
778                        }
779
780                        if ($p >= 3) {
781                            $c1      = \array_shift($temp_cw);
782                            $c2      = \array_shift($temp_cw);
783                            $c3      = \array_shift($temp_cw);
784                            $p      -= 3;
785                            $tmp     = ((1600 * $c1) + (40 * $c2) + $c3 + 1);
786                            $cw[]    = ($tmp >> 8);
787                            $cw[]    = ($tmp % 256);
788                            $cw_num += 2;
789                            $pos     = $epos;
790
791                            // 1. If the C40 encoding is at the point of starting a new double symbol character and if the look-ahead test (starting at step J) indicates another mode, switch to that mode.
792                            $newenc = $this->lookAheadTest($data, $pos, $enc);
793                            if ($newenc !== $enc) {
794                                $enc = $newenc;
795                                if ($enc !== self::ENC_ASCII) {
796                                    // set unlatch character
797                                    $cw[] = $this->getSwitchEncodingCodeword(self::ENC_ASCII);
798                                    ++$cw_num;
799                                }
800
801                                ++$cw_num;
802
803                                $cw[] = $this->getSwitchEncodingCodeword($enc);
804                                $pos -= $p;
805                                $p    = 0;
806
807                                break;
808                            }
809                        }
810                    } while (($p > 0) && ($epos < $data_length));
811
812                    // process last data (if any)
813                    if ($p > 0) {
814                        // get remaining number of data symbols
815                        $cwr = ($this->getMaxDataCodewords($cw_num) - $cw_num);
816
817                        if (($cwr === 1) && ($p === 1)) {
818                            // d. If one symbol character remains and one C40 value (data character) remains to be encoded
819                            $c1 = \array_shift($temp_cw);
820                            --$p;
821                            ++$cw_num;
822
823                            $cw[]           = ($chr + 1);
824                            $pos            = $epos;
825                            $enc            = self::ENC_ASCII;
826                            $this->encoding = $enc;
827                        } elseif (($cwr === 2) && ($p === 1)) {
828                            // c. If two symbol characters remain and only one C40 value (data character) remains to be encoded
829                            --$p;
830
831                            $c1             = \array_shift($temp_cw);
832                            $cw[]           = 254;
833                            $cw[]           = ($chr + 1);
834                            $cw_num        += 2;
835                            $pos            = $epos;
836                            $enc            = self::ENC_ASCII;
837                            $this->encoding = $enc;
838                        } elseif (($cwr === 2) && ($p === 2)) {
839                            // b. If two symbol characters remain and two C40 values remain to be encoded
840                            $c1             = \array_shift($temp_cw);
841                            $c2             = \array_shift($temp_cw);
842                            $p             -= 2;
843                            $tmp            = ((1600 * $c1) + (40 * $c2) + 1);
844                            $cw[]           = ($tmp >> 8);
845                            $cw[]           = ($tmp % 256);
846                            $cw_num        += 2;
847                            $pos            = $epos;
848                            $enc            = self::ENC_ASCII;
849                            $this->encoding = $enc;
850                        } elseif ($enc !== self::ENC_ASCII) {
851                            // switch to ASCII encoding
852                            $enc            = self::ENC_ASCII;
853                            $this->encoding = $enc;
854                            $cw[]           = $this->getSwitchEncodingCodeword($enc);
855
856                            ++$cw_num;
857
858                            $pos = ($epos - $p);
859                        }
860                    }
861                    break;
862                case self::ENC_EDF:
863                    // F. While in EDIFACT (EDF) encodation
864                    // initialize temporary array with 0 length
865                    $temp_cw      = [];
866                    $epos         = $pos;
867                    $field_length = 0;
868                    $newenc       = $enc;
869
870                    do {
871                        // 2. process the next character in EDIFACT encodation.
872                        $chr = \ord($data[$epos]);
873                        if ($this->isCharMode($chr, self::ENC_EDF)) {
874                            ++$epos;
875                            $temp_cw[] = $chr;
876                            ++$field_length;
877                        }
878
879                        if (($field_length === 4)
880                            || ($epos === $data_length)
881                            || !$this->isCharMode($chr, self::ENC_EDF)
882                        ) {
883                            if (($epos === $data_length) && ($field_length < 3)) {
884                                $enc  = self::ENC_ASCII;
885                                $cw[] = $this->getSwitchEncodingCodeword($enc);
886                                ++$cw_num;
887
888                                break;
889                            }
890
891                            if ($field_length < 4) {
892                                // set unlatch character
893                                $temp_cw[] = 0x1f;
894                                ++$field_length;
895
896                                // fill empty characters
897                                for ($i = $field_length; $i < 4; ++$i) {
898                                    $temp_cw[] = 0;
899                                }
900
901                                $enc            = self::ENC_ASCII;
902                                $this->encoding = $enc;
903                            }
904
905                            // encodes four data characters in three codewords
906                            $tcw = (($temp_cw[0] & 0x3F) << 2) + (($temp_cw[1] & 0x30) >> 4);
907                            if ($tcw > 0) {
908                                $cw[] = $tcw;
909                                ++$cw_num;
910                            }
911
912                            $tcw = (($temp_cw[1] & 0x0F) << 4) + (($temp_cw[2] & 0x3C) >> 2);
913                            if ($tcw > 0) {
914                                $cw[] = $tcw;
915                                ++$cw_num;
916                            }
917
918                            $tcw = (($temp_cw[2] & 0x03) << 6) + ($temp_cw[3] & 0x3F);
919                            if ($tcw > 0) {
920                                $cw[] = $tcw;
921                                ++$cw_num;
922                            }
923
924                            $temp_cw      = [];
925                            $pos          = $epos;
926                            $field_length = 0;
927
928                            if ($enc === self::ENC_ASCII) {
929                                break;
930                            }
931                        }
932                    } while ($epos < $data_length);
933
934                    break;
935                case self::ENC_BASE256:
936                    // G. While in Base 256 (B256) encodation
937                    // initialize temporary array with 0 length
938                    $temp_cw      = [];
939                    $field_length = 0;
940
941                    while (($pos < $data_length) && ($field_length <= 1555)) {
942                        $newenc = $this->lookAheadTest($data, $pos, $enc);
943
944                        if ($newenc !== $enc) {
945                            // 1. If the look-ahead test (starting at step J) indicates another mode, switch to that mode.
946                            $enc = $newenc;
947
948                            break;
949                        } else {
950                            // 2. Otherwise, process the next character in Base 256 encodation.
951                            $chr = \ord($data[$pos]);
952                            ++$pos;
953
954                            $temp_cw[] = $chr;
955
956                            ++$field_length;
957                        }
958                    }
959
960                    // set field length
961                    if ($field_length <= 249) {
962                        $cw[] = $this->get255StateCodeword($field_length, ($cw_num + 1));
963                        ++$cw_num;
964                    } else {
965                        $cw[]    = $this->get255StateCodeword((int) (\floor($field_length / 250) + 249), ($cw_num + 1));
966                        $cw[]    = $this->get255StateCodeword(($field_length % 250), ($cw_num + 2));
967                        $cw_num += 2;
968                    }
969
970                    // add B256 field
971                    foreach ($temp_cw as $p => $cht) {
972                        $cw[] = $this->get255StateCodeword($cht, ($cw_num + $p + 1));
973                    }
974
975                    break;
976            }
977        }
978
979        return $cw;
980    }
981
982    /**
983     * Places "chr+bit" with appropriate wrapping within array[].
984     */
985    protected function placeModule(array $marr, int $nrow, int $ncol, int $row, int $col, int $chr, int $bit) : array
986    {
987        if ($row < 0) {
988            $row += $nrow;
989            $col += (4 - (($nrow + 4) % 8));
990        }
991
992        if ($col < 0) {
993            $col += $ncol;
994            $row += (4 - (($ncol + 4) % 8));
995        }
996
997        $marr[(($row * $ncol) + $col)] = ((10 * $chr) + $bit);
998
999        return $marr;
1000    }
1001
1002    /**
1003     * Places the 8 bits of a utah-shaped symbol character.
1004     */
1005    protected function placeUtah(array $marr, int $nrow, int $ncol, int $row, int $col, int $chr) : array
1006    {
1007        $marr = $this->placeModule($marr, $nrow, $ncol, $row - 2, $col - 2, $chr, 1);
1008        $marr = $this->placeModule($marr, $nrow, $ncol, $row - 2, $col - 1, $chr, 2);
1009        $marr = $this->placeModule($marr, $nrow, $ncol, $row - 1, $col - 2, $chr, 3);
1010        $marr = $this->placeModule($marr, $nrow, $ncol, $row - 1, $col - 1, $chr, 4);
1011        $marr = $this->placeModule($marr, $nrow, $ncol, $row - 1, $col,   $chr, 5);
1012        $marr = $this->placeModule($marr, $nrow, $ncol, $row,   $col - 2, $chr, 6);
1013        $marr = $this->placeModule($marr, $nrow, $ncol, $row,   $col - 1, $chr, 7);
1014
1015        return $this->placeModule($marr, $nrow, $ncol, $row,   $col,   $chr, 8);
1016    }
1017
1018    /**
1019     * Places the 8 bits of the first special corner case.
1020     */
1021    protected function placeCornerA(array $marr, int $nrow, int $ncol, int $chr) : array
1022    {
1023        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 1, 0,       $chr, 1);
1024        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 1, 1,       $chr, 2);
1025        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 1, 2,       $chr, 3);
1026        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 2, $chr, 4);
1027        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 1, $chr, 5);
1028        $marr = $this->placeModule($marr, $nrow, $ncol, 1,       $ncol - 1, $chr, 6);
1029        $marr = $this->placeModule($marr, $nrow, $ncol, 2,       $ncol - 1, $chr, 7);
1030
1031        return $this->placeModule($marr, $nrow, $ncol, 3,       $ncol - 1, $chr, 8);
1032    }
1033
1034    /**
1035     * Places the 8 bits of the second special corner case.
1036     */
1037    protected function placeCornerB(array $marr, int $nrow, int $ncol, int $chr) : array
1038    {
1039        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 3, 0,       $chr, 1);
1040        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 2, 0,       $chr, 2);
1041        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 1, 0,       $chr, 3);
1042        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 4, $chr, 4);
1043        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 3, $chr, 5);
1044        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 2, $chr, 6);
1045        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 1, $chr, 7);
1046
1047        return $this->placeModule($marr, $nrow, $ncol, 1,       $ncol - 1, $chr, 8);
1048    }
1049
1050    /**
1051     * Places the 8 bits of the third special corner case.
1052     */
1053    protected function placeCornerC(array $marr, int $nrow, int $ncol, int $chr) : array
1054    {
1055        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 3, 0,       $chr, 1);
1056        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 2, 0,       $chr, 2);
1057        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 1, 0,       $chr, 3);
1058        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 2, $chr, 4);
1059        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 1, $chr, 5);
1060        $marr = $this->placeModule($marr, $nrow, $ncol, 1,       $ncol - 1, $chr, 6);
1061        $marr = $this->placeModule($marr, $nrow, $ncol, 2,       $ncol - 1, $chr, 7);
1062
1063        return $this->placeModule($marr, $nrow, $ncol, 3,       $ncol - 1, $chr, 8);
1064    }
1065
1066    /**
1067     * Places the 8 bits of the fourth special corner case.
1068     */
1069    protected function placeCornerD(array $marr, int $nrow, int $ncol, int $chr) : array
1070    {
1071        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 1, 0,       $chr, 1);
1072        $marr = $this->placeModule($marr, $nrow, $ncol, $nrow - 1, $ncol - 1, $chr, 2);
1073        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 3, $chr, 3);
1074        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 2, $chr, 4);
1075        $marr = $this->placeModule($marr, $nrow, $ncol, 0,       $ncol - 1, $chr, 5);
1076        $marr = $this->placeModule($marr, $nrow, $ncol, 1,       $ncol - 3, $chr, 6);
1077        $marr = $this->placeModule($marr, $nrow, $ncol, 1,       $ncol - 2, $chr, 7);
1078
1079        return $this->placeModule($marr, $nrow, $ncol, 1,       $ncol - 1, $chr, 8);
1080    }
1081
1082    /**
1083     * Build a placement map.
1084     */
1085    protected function getPlacementMap(int $nrow, int $ncol) : array
1086    {
1087        // initialize array with zeros
1088        $marr = \array_fill(0, ($nrow * $ncol), 0);
1089
1090        // set starting values
1091        $chr = 1;
1092        $row = 4;
1093        $col = 0;
1094
1095        do {
1096            // repeatedly first check for one of the special corner cases, then
1097            if (($row === $nrow) && ($col === 0)) {
1098                $marr = $this->placeCornerA($marr, $nrow, $ncol, $chr);
1099                ++$chr;
1100            } elseif (($row === ($nrow - 2)) && ($col === 0) && ($ncol % 4)) {
1101                $marr = $this->placeCornerB($marr, $nrow, $ncol, $chr);
1102                ++$chr;
1103            } elseif (($row === ($nrow - 2)) && ($col === 0) && (($ncol % 8) === 4)) {
1104                $marr = $this->placeCornerC($marr, $nrow, $ncol, $chr);
1105                ++$chr;
1106            } elseif (($row === ($nrow + 4)) && ($col === 2) && (!($ncol % 8))) {
1107                $marr = $this->placeCornerD($marr, $nrow, $ncol, $chr);
1108                ++$chr;
1109            }
1110
1111            // sweep upward diagonally, inserting successive characters,
1112            do {
1113                if (($row < $nrow) && ($col >= 0) && (!$marr[(($row * $ncol) + $col)])) {
1114                    $marr = $this->placeUtah($marr, $nrow, $ncol, $row, $col, $chr);
1115                    ++$chr;
1116                }
1117
1118                $row -= 2;
1119                $col += 2;
1120            } while (($row >= 0) && ($col < $ncol));
1121
1122            ++$row;
1123            $col += 3;
1124
1125            // & then sweep downward diagonally, inserting successive characters,...
1126            do {
1127                if (($row >= 0) && ($col < $ncol) && (!$marr[(($row * $ncol) + $col)])) {
1128                    $marr = $this->placeUtah($marr, $nrow, $ncol, $row, $col, $chr);
1129                    ++$chr;
1130                }
1131
1132                $row += 2;
1133                $col -= 2;
1134            } while (($row < $nrow) && ($col >= 0));
1135
1136            $row += 3;
1137            ++$col;
1138        } while (($row < $nrow) || ($col < $ncol));
1139
1140        // lastly, if the lower righthand corner is untouched, fill in fixed pattern
1141        if (!$marr[(($nrow * $ncol) - 1)]) {
1142            $marr[(($nrow * $ncol) - 1)]         = 1;
1143            $marr[(($nrow * $ncol) - $ncol - 2)] = 1;
1144        }
1145
1146        return $marr;
1147    }
1148}