PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.4
Booking for Appointments and Events Calendar – Amelia v2.4.4
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / bacon / bacon-qr-code / src / Encoder / Encoder.php
ameliabooking / vendor / bacon / bacon-qr-code / src / Encoder Last commit date
BlockPair.php 8 months ago ByteMatrix.php 8 months ago Encoder.php 8 months ago MaskUtil.php 8 months ago MatrixUtil.php 8 months ago QrCode.php 8 months ago
Encoder.php
669 lines
1 <?php
2 declare(strict_types = 1);
3
4 namespace BaconQrCode\Encoder;
5
6 use BaconQrCode\Common\BitArray;
7 use BaconQrCode\Common\CharacterSetEci;
8 use BaconQrCode\Common\ErrorCorrectionLevel;
9 use BaconQrCode\Common\Mode;
10 use BaconQrCode\Common\ReedSolomonCodec;
11 use BaconQrCode\Common\Version;
12 use BaconQrCode\Exception\WriterException;
13 use SplFixedArray;
14
15 /**
16 * Encoder.
17 */
18 final class Encoder
19 {
20 /**
21 * Default byte encoding.
22 */
23 public const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1';
24
25 /**
26 * The original table is defined in the table 5 of JISX0510:2004 (p.19).
27 */
28 private const ALPHANUMERIC_TABLE = [
29 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
30 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
31 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
32 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
33 -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
34 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
35 ];
36
37 /**
38 * Codec cache.
39 *
40 * @var array<string,ReedSolomonCodec>
41 */
42 private static $codecs = [];
43
44 /**
45 * Encodes "content" with the error correction level "ecLevel".
46 */
47 public static function encode(
48 string $content,
49 ErrorCorrectionLevel $ecLevel,
50 string $encoding = self::DEFAULT_BYTE_MODE_ECODING,
51 ?Version $forcedVersion = null
52 ) : QrCode {
53 // Pick an encoding mode appropriate for the content. Note that this
54 // will not attempt to use multiple modes / segments even if that were
55 // more efficient. Would be nice.
56 $mode = self::chooseMode($content, $encoding);
57
58 // This will store the header information, like mode and length, as well
59 // as "header" segments like an ECI segment.
60 $headerBits = new BitArray();
61
62 // Append ECI segment if applicable
63 if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) {
64 $eci = CharacterSetEci::getCharacterSetEciByName($encoding);
65
66 if (null !== $eci) {
67 self::appendEci($eci, $headerBits);
68 }
69 }
70
71 // (With ECI in place,) Write the mode marker
72 self::appendModeInfo($mode, $headerBits);
73
74 // Collect data within the main segment, separately, to count its size
75 // if needed. Don't add it to main payload yet.
76 $dataBits = new BitArray();
77 self::appendBytes($content, $mode, $dataBits, $encoding);
78
79 // Hard part: need to know version to know how many bits length takes.
80 // But need to know how many bits it takes to know version. First we
81 // take a guess at version by assuming version will be the minimum, 1:
82 $provisionalBitsNeeded = $headerBits->getSize()
83 + $mode->getCharacterCountBits(Version::getVersionForNumber(1))
84 + $dataBits->getSize();
85 $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel);
86
87 // Use that guess to calculate the right version. I am still not sure
88 // this works in 100% of cases.
89 $bitsNeeded = $headerBits->getSize()
90 + $mode->getCharacterCountBits($provisionalVersion)
91 + $dataBits->getSize();
92 $version = self::chooseVersion($bitsNeeded, $ecLevel);
93
94 if (null !== $forcedVersion) {
95 // Forced version check
96 if ($version->getVersionNumber() <= $forcedVersion->getVersionNumber()) {
97 // Calculated minimum version is same or equal as forced version
98 $version = $forcedVersion;
99 } else {
100 throw new WriterException(
101 'Invalid version! Calculated version: '
102 . $version->getVersionNumber()
103 . ', requested version: '
104 . $forcedVersion->getVersionNumber()
105 );
106 }
107 }
108
109 $headerAndDataBits = new BitArray();
110 $headerAndDataBits->appendBitArray($headerBits);
111
112 // Find "length" of main segment and write it.
113 $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content));
114 self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits);
115
116 // Put data together into the overall payload.
117 $headerAndDataBits->appendBitArray($dataBits);
118 $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
119 $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords();
120
121 // Terminate the bits properly.
122 self::terminateBits($numDataBytes, $headerAndDataBits);
123
124 // Interleave data bits with error correction code.
125 $finalBits = self::interleaveWithEcBytes(
126 $headerAndDataBits,
127 $version->getTotalCodewords(),
128 $numDataBytes,
129 $ecBlocks->getNumBlocks()
130 );
131
132 // Choose the mask pattern.
133 $dimension = $version->getDimensionForVersion();
134 $matrix = new ByteMatrix($dimension, $dimension);
135 $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix);
136
137 // Build the matrix.
138 MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix);
139
140 return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix);
141 }
142
143 /**
144 * Gets the alphanumeric code for a byte.
145 */
146 private static function getAlphanumericCode(int $code) : int
147 {
148 if (isset(self::ALPHANUMERIC_TABLE[$code])) {
149 return self::ALPHANUMERIC_TABLE[$code];
150 }
151
152 return -1;
153 }
154
155 /**
156 * Chooses the best mode for a given content.
157 */
158 private static function chooseMode(string $content, string $encoding = null) : Mode
159 {
160 if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) {
161 return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE();
162 }
163
164 $hasNumeric = false;
165 $hasAlphanumeric = false;
166 $contentLength = strlen($content);
167
168 for ($i = 0; $i < $contentLength; ++$i) {
169 $char = $content[$i];
170
171 if (ctype_digit($char)) {
172 $hasNumeric = true;
173 } elseif (-1 !== self::getAlphanumericCode(ord($char))) {
174 $hasAlphanumeric = true;
175 } else {
176 return Mode::BYTE();
177 }
178 }
179
180 if ($hasAlphanumeric) {
181 return Mode::ALPHANUMERIC();
182 } elseif ($hasNumeric) {
183 return Mode::NUMERIC();
184 }
185
186 return Mode::BYTE();
187 }
188
189 /**
190 * Calculates the mask penalty for a matrix.
191 */
192 private static function calculateMaskPenalty(ByteMatrix $matrix) : int
193 {
194 return (
195 MaskUtil::applyMaskPenaltyRule1($matrix)
196 + MaskUtil::applyMaskPenaltyRule2($matrix)
197 + MaskUtil::applyMaskPenaltyRule3($matrix)
198 + MaskUtil::applyMaskPenaltyRule4($matrix)
199 );
200 }
201
202 /**
203 * Checks if content only consists of double-byte kanji characters.
204 */
205 private static function isOnlyDoubleByteKanji(string $content) : bool
206 {
207 $bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
208
209 if (false === $bytes) {
210 return false;
211 }
212
213 $length = strlen($bytes);
214
215 if (0 !== $length % 2) {
216 return false;
217 }
218
219 for ($i = 0; $i < $length; $i += 2) {
220 $byte = $bytes[$i] & 0xff;
221
222 if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) {
223 return false;
224 }
225 }
226
227 return true;
228 }
229
230 /**
231 * Chooses the best mask pattern for a matrix.
232 */
233 private static function chooseMaskPattern(
234 BitArray $bits,
235 ErrorCorrectionLevel $ecLevel,
236 Version $version,
237 ByteMatrix $matrix
238 ) : int {
239 $minPenalty = PHP_INT_MAX;
240 $bestMaskPattern = -1;
241
242 for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) {
243 MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix);
244 $penalty = self::calculateMaskPenalty($matrix);
245
246 if ($penalty < $minPenalty) {
247 $minPenalty = $penalty;
248 $bestMaskPattern = $maskPattern;
249 }
250 }
251
252 return $bestMaskPattern;
253 }
254
255 /**
256 * Chooses the best version for the input.
257 *
258 * @throws WriterException if data is too big
259 */
260 private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version
261 {
262 for ($versionNum = 1; $versionNum <= 40; ++$versionNum) {
263 $version = Version::getVersionForNumber($versionNum);
264 $numBytes = $version->getTotalCodewords();
265
266 $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
267 $numEcBytes = $ecBlocks->getTotalEcCodewords();
268
269 $numDataBytes = $numBytes - $numEcBytes;
270 $totalInputBytes = intdiv($numInputBits + 8, 8);
271
272 if ($numDataBytes >= $totalInputBytes) {
273 return $version;
274 }
275 }
276
277 throw new WriterException('Data too big');
278 }
279
280 /**
281 * Terminates the bits in a bit array.
282 *
283 * @throws WriterException if data bits cannot fit in the QR code
284 * @throws WriterException if bits size does not equal the capacity
285 */
286 private static function terminateBits(int $numDataBytes, BitArray $bits) : void
287 {
288 $capacity = $numDataBytes << 3;
289
290 if ($bits->getSize() > $capacity) {
291 throw new WriterException('Data bits cannot fit in the QR code');
292 }
293
294 for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) {
295 $bits->appendBit(false);
296 }
297
298 $numBitsInLastByte = $bits->getSize() & 0x7;
299
300 if ($numBitsInLastByte > 0) {
301 for ($i = $numBitsInLastByte; $i < 8; ++$i) {
302 $bits->appendBit(false);
303 }
304 }
305
306 $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes();
307
308 for ($i = 0; $i < $numPaddingBytes; ++$i) {
309 $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8);
310 }
311
312 if ($bits->getSize() !== $capacity) {
313 throw new WriterException('Bits size does not equal capacity');
314 }
315 }
316
317 /**
318 * Gets number of data- and EC bytes for a block ID.
319 *
320 * @return int[]
321 * @throws WriterException if block ID is too large
322 * @throws WriterException if EC bytes mismatch
323 * @throws WriterException if RS blocks mismatch
324 * @throws WriterException if total bytes mismatch
325 */
326 private static function getNumDataBytesAndNumEcBytesForBlockId(
327 int $numTotalBytes,
328 int $numDataBytes,
329 int $numRsBlocks,
330 int $blockId
331 ) : array {
332 if ($blockId >= $numRsBlocks) {
333 throw new WriterException('Block ID too large');
334 }
335
336 $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks;
337 $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2;
338 $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks);
339 $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1;
340 $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks);
341 $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1;
342 $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1;
343 $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2;
344
345 if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) {
346 throw new WriterException('EC bytes mismatch');
347 }
348
349 if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) {
350 throw new WriterException('RS blocks mismatch');
351 }
352
353 if ($numTotalBytes !==
354 (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1)
355 + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2)
356 ) {
357 throw new WriterException('Total bytes mismatch');
358 }
359
360 if ($blockId < $numRsBlocksInGroup1) {
361 return [$numDataBytesInGroup1, $numEcBytesInGroup1];
362 } else {
363 return [$numDataBytesInGroup2, $numEcBytesInGroup2];
364 }
365 }
366
367 /**
368 * Interleaves data with EC bytes.
369 *
370 * @throws WriterException if number of bits and data bytes does not match
371 * @throws WriterException if data bytes does not match offset
372 * @throws WriterException if an interleaving error occurs
373 */
374 private static function interleaveWithEcBytes(
375 BitArray $bits,
376 int $numTotalBytes,
377 int $numDataBytes,
378 int $numRsBlocks
379 ) : BitArray {
380 if ($bits->getSizeInBytes() !== $numDataBytes) {
381 throw new WriterException('Number of bits and data bytes does not match');
382 }
383
384 $dataBytesOffset = 0;
385 $maxNumDataBytes = 0;
386 $maxNumEcBytes = 0;
387
388 $blocks = new SplFixedArray($numRsBlocks);
389
390 for ($i = 0; $i < $numRsBlocks; ++$i) {
391 list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId(
392 $numTotalBytes,
393 $numDataBytes,
394 $numRsBlocks,
395 $i
396 );
397
398 $size = $numDataBytesInBlock;
399 $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size);
400 $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock);
401 $blocks[$i] = new BlockPair($dataBytes, $ecBytes);
402
403 $maxNumDataBytes = max($maxNumDataBytes, $size);
404 $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes));
405 $dataBytesOffset += $numDataBytesInBlock;
406 }
407
408 if ($numDataBytes !== $dataBytesOffset) {
409 throw new WriterException('Data bytes does not match offset');
410 }
411
412 $result = new BitArray();
413
414 for ($i = 0; $i < $maxNumDataBytes; ++$i) {
415 foreach ($blocks as $block) {
416 $dataBytes = $block->getDataBytes();
417
418 if ($i < count($dataBytes)) {
419 $result->appendBits($dataBytes[$i], 8);
420 }
421 }
422 }
423
424 for ($i = 0; $i < $maxNumEcBytes; ++$i) {
425 foreach ($blocks as $block) {
426 $ecBytes = $block->getErrorCorrectionBytes();
427
428 if ($i < count($ecBytes)) {
429 $result->appendBits($ecBytes[$i], 8);
430 }
431 }
432 }
433
434 if ($numTotalBytes !== $result->getSizeInBytes()) {
435 throw new WriterException(
436 'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ'
437 );
438 }
439
440 return $result;
441 }
442
443 /**
444 * Generates EC bytes for given data.
445 *
446 * @param SplFixedArray<int> $dataBytes
447 * @return SplFixedArray<int>
448 */
449 private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray
450 {
451 $numDataBytes = count($dataBytes);
452 $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock);
453
454 for ($i = 0; $i < $numDataBytes; $i++) {
455 $toEncode[$i] = $dataBytes[$i] & 0xff;
456 }
457
458 $ecBytes = new SplFixedArray($numEcBytesInBlock);
459 $codec = self::getCodec($numDataBytes, $numEcBytesInBlock);
460 $codec->encode($toEncode, $ecBytes);
461
462 return $ecBytes;
463 }
464
465 /**
466 * Gets an RS codec and caches it.
467 */
468 private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec
469 {
470 $cacheId = $numDataBytes . '-' . $numEcBytesInBlock;
471
472 if (isset(self::$codecs[$cacheId])) {
473 return self::$codecs[$cacheId];
474 }
475
476 return self::$codecs[$cacheId] = new ReedSolomonCodec(
477 8,
478 0x11d,
479 0,
480 1,
481 $numEcBytesInBlock,
482 255 - $numDataBytes - $numEcBytesInBlock
483 );
484 }
485
486 /**
487 * Appends mode information to a bit array.
488 */
489 private static function appendModeInfo(Mode $mode, BitArray $bits) : void
490 {
491 $bits->appendBits($mode->getBits(), 4);
492 }
493
494 /**
495 * Appends length information to a bit array.
496 *
497 * @throws WriterException if num letters is bigger than expected
498 */
499 private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void
500 {
501 $numBits = $mode->getCharacterCountBits($version);
502
503 if ($numLetters >= (1 << $numBits)) {
504 throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1));
505 }
506
507 $bits->appendBits($numLetters, $numBits);
508 }
509
510 /**
511 * Appends bytes to a bit array in a specific mode.
512 *
513 * @throws WriterException if an invalid mode was supplied
514 */
515 private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void
516 {
517 switch ($mode) {
518 case Mode::NUMERIC():
519 self::appendNumericBytes($content, $bits);
520 break;
521
522 case Mode::ALPHANUMERIC():
523 self::appendAlphanumericBytes($content, $bits);
524 break;
525
526 case Mode::BYTE():
527 self::append8BitBytes($content, $bits, $encoding);
528 break;
529
530 case Mode::KANJI():
531 self::appendKanjiBytes($content, $bits);
532 break;
533
534 default:
535 throw new WriterException('Invalid mode: ' . $mode);
536 }
537 }
538
539 /**
540 * Appends numeric bytes to a bit array.
541 */
542 private static function appendNumericBytes(string $content, BitArray $bits) : void
543 {
544 $length = strlen($content);
545 $i = 0;
546
547 while ($i < $length) {
548 $num1 = (int) $content[$i];
549
550 if ($i + 2 < $length) {
551 // Encode three numeric letters in ten bits.
552 $num2 = (int) $content[$i + 1];
553 $num3 = (int) $content[$i + 2];
554 $bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10);
555 $i += 3;
556 } elseif ($i + 1 < $length) {
557 // Encode two numeric letters in seven bits.
558 $num2 = (int) $content[$i + 1];
559 $bits->appendBits($num1 * 10 + $num2, 7);
560 $i += 2;
561 } else {
562 // Encode one numeric letter in four bits.
563 $bits->appendBits($num1, 4);
564 ++$i;
565 }
566 }
567 }
568
569 /**
570 * Appends alpha-numeric bytes to a bit array.
571 *
572 * @throws WriterException if an invalid alphanumeric code was found
573 */
574 private static function appendAlphanumericBytes(string $content, BitArray $bits) : void
575 {
576 $length = strlen($content);
577 $i = 0;
578
579 while ($i < $length) {
580 $code1 = self::getAlphanumericCode(ord($content[$i]));
581
582 if (-1 === $code1) {
583 throw new WriterException('Invalid alphanumeric code');
584 }
585
586 if ($i + 1 < $length) {
587 $code2 = self::getAlphanumericCode(ord($content[$i + 1]));
588
589 if (-1 === $code2) {
590 throw new WriterException('Invalid alphanumeric code');
591 }
592
593 // Encode two alphanumeric letters in 11 bits.
594 $bits->appendBits($code1 * 45 + $code2, 11);
595 $i += 2;
596 } else {
597 // Encode one alphanumeric letter in six bits.
598 $bits->appendBits($code1, 6);
599 ++$i;
600 }
601 }
602 }
603
604 /**
605 * Appends regular 8-bit bytes to a bit array.
606 *
607 * @throws WriterException if content cannot be encoded to target encoding
608 */
609 private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void
610 {
611 $bytes = @iconv('utf-8', $encoding, $content);
612
613 if (false === $bytes) {
614 throw new WriterException('Could not encode content to ' . $encoding);
615 }
616
617 $length = strlen($bytes);
618
619 for ($i = 0; $i < $length; $i++) {
620 $bits->appendBits(ord($bytes[$i]), 8);
621 }
622 }
623
624 /**
625 * Appends KANJI bytes to a bit array.
626 *
627 * @throws WriterException if content does not seem to be encoded in SHIFT-JIS
628 * @throws WriterException if an invalid byte sequence occurs
629 */
630 private static function appendKanjiBytes(string $content, BitArray $bits) : void
631 {
632 if (strlen($content) % 2 > 0) {
633 // We just do a simple length check here. The for loop will check
634 // individual characters.
635 throw new WriterException('Content does not seem to be encoded in SHIFT-JIS');
636 }
637
638 $length = strlen($content);
639
640 for ($i = 0; $i < $length; $i += 2) {
641 $byte1 = ord($content[$i]) & 0xff;
642 $byte2 = ord($content[$i + 1]) & 0xff;
643 $code = ($byte1 << 8) | $byte2;
644
645 if ($code >= 0x8140 && $code <= 0x9ffc) {
646 $subtracted = $code - 0x8140;
647 } elseif ($code >= 0xe040 && $code <= 0xebbf) {
648 $subtracted = $code - 0xc140;
649 } else {
650 throw new WriterException('Invalid byte sequence');
651 }
652
653 $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff);
654
655 $bits->appendBits($encoded, 13);
656 }
657 }
658
659 /**
660 * Appends ECI information to a bit array.
661 */
662 private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void
663 {
664 $mode = Mode::ECI();
665 $bits->appendBits($mode->getBits(), 4);
666 $bits->appendBits($eci->getValue(), 8);
667 }
668 }
669