uawdijnntqw1x1x1
IP : 216.73.216.136
Hostname : dhandapanilive
Kernel : Linux dhandapanilive 5.15.0-139-generic #149~20.04.1-Ubuntu SMP Wed Apr 16 08:29:56 UTC 2025 x86_64
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
OS : Linux
PATH:
/
var
/
www
/
html
/
dhandapani
/
968c0
/
..
/
dev
/
.
/
..
/
9da53
/
bacon-qr-code.tar
/
/
src/Writer.php000077700000003260151323635260007336 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Encoder\Encoder; use BaconQrCode\Exception\InvalidArgumentException; use BaconQrCode\Renderer\RendererInterface; /** * QR code writer. */ final class Writer { /** * Renderer instance. * * @var RendererInterface */ private $renderer; /** * Creates a new writer with a specific renderer. */ public function __construct(RendererInterface $renderer) { $this->renderer = $renderer; } /** * Writes QR code and returns it as string. * * Content is a string which *should* be encoded in UTF-8, in case there are * non ASCII-characters present. * * @throws InvalidArgumentException if the content is empty */ public function writeString( string $content, string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, ?ErrorCorrectionLevel $ecLevel = null ) : string { if (strlen($content) === 0) { throw new InvalidArgumentException('Found empty contents'); } if (null === $ecLevel) { $ecLevel = ErrorCorrectionLevel::L(); } return $this->renderer->render(Encoder::encode($content, $ecLevel, $encoding)); } /** * Writes QR code to a file. * * @see Writer::writeString() */ public function writeFile( string $content, string $filename, string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING, ?ErrorCorrectionLevel $ecLevel = null ) : void { file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel)); } } src/Common/ErrorCorrectionLevel.php000077700000002475151323635260013432 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; use BaconQrCode\Exception\OutOfBoundsException; use DASPRiD\Enum\AbstractEnum; /** * Enum representing the four error correction levels. * * @method static self L() ~7% correction * @method static self M() ~15% correction * @method static self Q() ~25% correction * @method static self H() ~30% correction */ final class ErrorCorrectionLevel extends AbstractEnum { protected const L = [0x01]; protected const M = [0x00]; protected const Q = [0x03]; protected const H = [0x02]; /** * @var int */ private $bits; protected function __construct(int $bits) { $this->bits = $bits; } /** * @throws OutOfBoundsException if number of bits is invalid */ public static function forBits(int $bits) : self { switch ($bits) { case 0: return self::M(); case 1: return self::L(); case 2: return self::H(); case 3: return self::Q(); } throw new OutOfBoundsException('Invalid number of bits'); } /** * Returns the two bits used to encode this error correction level. */ public function getBits() : int { return $this->bits; } } src/Common/EcBlocks.php000077700000003212151323635260010774 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; /** * Encapsulates a set of error-correction blocks in one symbol version. * * Most versions will use blocks of differing sizes within one version, so, this encapsulates the parameters for each * set of blocks. It also holds the number of error-correction codewords per block since it will be the same across all * blocks within one version. */ final class EcBlocks { /** * Number of EC codewords per block. * * @var int */ private $ecCodewordsPerBlock; /** * List of EC blocks. * * @var EcBlock[] */ private $ecBlocks; public function __construct(int $ecCodewordsPerBlock, EcBlock ...$ecBlocks) { $this->ecCodewordsPerBlock = $ecCodewordsPerBlock; $this->ecBlocks = $ecBlocks; } /** * Returns the number of EC codewords per block. */ public function getEcCodewordsPerBlock() : int { return $this->ecCodewordsPerBlock; } /** * Returns the total number of EC block appearances. */ public function getNumBlocks() : int { $total = 0; foreach ($this->ecBlocks as $ecBlock) { $total += $ecBlock->getCount(); } return $total; } /** * Returns the total count of EC codewords. */ public function getTotalEcCodewords() : int { return $this->ecCodewordsPerBlock * $this->getNumBlocks(); } /** * Returns the EC blocks included in this collection. * * @return EcBlock[] */ public function getEcBlocks() : array { return $this->ecBlocks; } } src/Common/BitArray.php000077700000021323151323635260011027 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; use BaconQrCode\Exception\InvalidArgumentException; use SplFixedArray; /** * A simple, fast array of bits. */ final class BitArray { /** * Bits represented as an array of integers. * * @var SplFixedArray<int> */ private $bits; /** * Size of the bit array in bits. * * @var int */ private $size; /** * Creates a new bit array with a given size. */ public function __construct(int $size = 0) { $this->size = $size; $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0)); } /** * Gets the size in bits. */ public function getSize() : int { return $this->size; } /** * Gets the size in bytes. */ public function getSizeInBytes() : int { return ($this->size + 7) >> 3; } /** * Ensures that the array has a minimum capacity. */ public function ensureCapacity(int $size) : void { if ($size > count($this->bits) << 5) { $this->bits->setSize(($size + 31) >> 5); } } /** * Gets a specific bit. */ public function get(int $i) : bool { return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f))); } /** * Sets a specific bit. */ public function set(int $i) : void { $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f); } /** * Flips a specific bit. */ public function flip(int $i) : void { $this->bits[$i >> 5] ^= 1 << ($i & 0x1f); } /** * Gets the next set bit position from a given position. */ public function getNextSet(int $from) : int { if ($from >= $this->size) { return $this->size; } $bitsOffset = $from >> 5; $currentBits = $this->bits[$bitsOffset]; $bitsLength = count($this->bits); $currentBits &= ~((1 << ($from & 0x1f)) - 1); while (0 === $currentBits) { if (++$bitsOffset === $bitsLength) { return $this->size; } $currentBits = $this->bits[$bitsOffset]; } $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); return $result > $this->size ? $this->size : $result; } /** * Gets the next unset bit position from a given position. */ public function getNextUnset(int $from) : int { if ($from >= $this->size) { return $this->size; } $bitsOffset = $from >> 5; $currentBits = ~$this->bits[$bitsOffset]; $bitsLength = count($this->bits); $currentBits &= ~((1 << ($from & 0x1f)) - 1); while (0 === $currentBits) { if (++$bitsOffset === $bitsLength) { return $this->size; } $currentBits = ~$this->bits[$bitsOffset]; } $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits); return $result > $this->size ? $this->size : $result; } /** * Sets a bulk of bits. */ public function setBulk(int $i, int $newBits) : void { $this->bits[$i >> 5] = $newBits; } /** * Sets a range of bits. * * @throws InvalidArgumentException if end is smaller than start */ public function setRange(int $start, int $end) : void { if ($end < $start) { throw new InvalidArgumentException('End must be greater or equal to start'); } if ($end === $start) { return; } --$end; $firstInt = $start >> 5; $lastInt = $end >> 5; for ($i = $firstInt; $i <= $lastInt; ++$i) { $firstBit = $i > $firstInt ? 0 : $start & 0x1f; $lastBit = $i < $lastInt ? 31 : $end & 0x1f; if (0 === $firstBit && 31 === $lastBit) { $mask = 0x7fffffff; } else { $mask = 0; for ($j = $firstBit; $j < $lastBit; ++$j) { $mask |= 1 << $j; } } $this->bits[$i] = $this->bits[$i] | $mask; } } /** * Clears the bit array, unsetting every bit. */ public function clear() : void { $bitsLength = count($this->bits); for ($i = 0; $i < $bitsLength; ++$i) { $this->bits[$i] = 0; } } /** * Checks if a range of bits is set or not set. * @throws InvalidArgumentException if end is smaller than start */ public function isRange(int $start, int $end, bool $value) : bool { if ($end < $start) { throw new InvalidArgumentException('End must be greater or equal to start'); } if ($end === $start) { return true; } --$end; $firstInt = $start >> 5; $lastInt = $end >> 5; for ($i = $firstInt; $i <= $lastInt; ++$i) { $firstBit = $i > $firstInt ? 0 : $start & 0x1f; $lastBit = $i < $lastInt ? 31 : $end & 0x1f; if (0 === $firstBit && 31 === $lastBit) { $mask = 0x7fffffff; } else { $mask = 0; for ($j = $firstBit; $j <= $lastBit; ++$j) { $mask |= 1 << $j; } } if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) { return false; } } return true; } /** * Appends a bit to the array. */ public function appendBit(bool $bit) : void { $this->ensureCapacity($this->size + 1); if ($bit) { $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f)); } ++$this->size; } /** * Appends a number of bits (up to 32) to the array. * @throws InvalidArgumentException if num bits is not between 0 and 32 */ public function appendBits(int $value, int $numBits) : void { if ($numBits < 0 || $numBits > 32) { throw new InvalidArgumentException('Num bits must be between 0 and 32'); } $this->ensureCapacity($this->size + $numBits); for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) { $this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1); } } /** * Appends another bit array to this array. */ public function appendBitArray(self $other) : void { $otherSize = $other->getSize(); $this->ensureCapacity($this->size + $other->getSize()); for ($i = 0; $i < $otherSize; ++$i) { $this->appendBit($other->get($i)); } } /** * Makes an exclusive-or comparision on the current bit array. * * @throws InvalidArgumentException if sizes don't match */ public function xorBits(self $other) : void { $bitsLength = count($this->bits); $otherBits = $other->getBitArray(); if ($bitsLength !== count($otherBits)) { throw new InvalidArgumentException('Sizes don\'t match'); } for ($i = 0; $i < $bitsLength; ++$i) { $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i]; } } /** * Converts the bit array to a byte array. * * @return SplFixedArray<int> */ public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray { $bytes = new SplFixedArray($numBytes); for ($i = 0; $i < $numBytes; ++$i) { $byte = 0; for ($j = 0; $j < 8; ++$j) { if ($this->get($bitOffset)) { $byte |= 1 << (7 - $j); } ++$bitOffset; } $bytes[$i] = $byte; } return $bytes; } /** * Gets the internal bit array. * * @return SplFixedArray<int> */ public function getBitArray() : SplFixedArray { return $this->bits; } /** * Reverses the array. */ public function reverse() : void { $newBits = new SplFixedArray(count($this->bits)); for ($i = 0; $i < $this->size; ++$i) { if ($this->get($this->size - $i - 1)) { $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f)); } } $this->bits = $newBits; } /** * Returns a string representation of the bit array. */ public function __toString() : string { $result = ''; for ($i = 0; $i < $this->size; ++$i) { if (0 === ($i & 0x07)) { $result .= ' '; } $result .= $this->get($i) ? 'X' : '.'; } return $result; } } src/Common/EcBlock.php000077700000001750151323635260010616 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; /** * Encapsulates the parameters for one error-correction block in one symbol version. * * This includes the number of data codewords, and the number of times a block with these parameters is used * consecutively in the QR code version's format. */ final class EcBlock { /** * How many times the block is used. * * @var int */ private $count; /** * Number of data codewords. * * @var int */ private $dataCodewords; public function __construct(int $count, int $dataCodewords) { $this->count = $count; $this->dataCodewords = $dataCodewords; } /** * Returns how many times the block is used. */ public function getCount() : int { return $this->count; } /** * Returns the number of data codewords. */ public function getDataCodewords() : int { return $this->dataCodewords; } } src/Common/ReedSolomonCodec.php000077700000035071151323635260012503 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; use BaconQrCode\Exception\InvalidArgumentException; use BaconQrCode\Exception\RuntimeException; use SplFixedArray; /** * Reed-Solomon codec for 8-bit characters. * * Based on libfec by Phil Karn, KA9Q. */ final class ReedSolomonCodec { /** * Symbol size in bits. * * @var int */ private $symbolSize; /** * Block size in symbols. * * @var int */ private $blockSize; /** * First root of RS code generator polynomial, index form. * * @var int */ private $firstRoot; /** * Primitive element to generate polynomial roots, index form. * * @var int */ private $primitive; /** * Prim-th root of 1, index form. * * @var int */ private $iPrimitive; /** * RS code generator polynomial degree (number of roots). * * @var int */ private $numRoots; /** * Padding bytes at front of shortened block. * * @var int */ private $padding; /** * Log lookup table. * * @var SplFixedArray */ private $alphaTo; /** * Anti-Log lookup table. * * @var SplFixedArray */ private $indexOf; /** * Generator polynomial. * * @var SplFixedArray */ private $generatorPoly; /** * @throws InvalidArgumentException if symbol size ist not between 0 and 8 * @throws InvalidArgumentException if first root is invalid * @throws InvalidArgumentException if num roots is invalid * @throws InvalidArgumentException if padding is invalid * @throws RuntimeException if field generator polynomial is not primitive */ public function __construct( int $symbolSize, int $gfPoly, int $firstRoot, int $primitive, int $numRoots, int $padding ) { if ($symbolSize < 0 || $symbolSize > 8) { throw new InvalidArgumentException('Symbol size must be between 0 and 8'); } if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) { throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize)); } if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) { throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize)); } if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) { throw new InvalidArgumentException( 'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots) ); } $this->symbolSize = $symbolSize; $this->blockSize = (1 << $symbolSize) - 1; $this->padding = $padding; $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false); // Generate galous field lookup table $this->indexOf[0] = $this->blockSize; $this->alphaTo[$this->blockSize] = 0; $sr = 1; for ($i = 0; $i < $this->blockSize; ++$i) { $this->indexOf[$sr] = $i; $this->alphaTo[$i] = $sr; $sr <<= 1; if ($sr & (1 << $symbolSize)) { $sr ^= $gfPoly; } $sr &= $this->blockSize; } if (1 !== $sr) { throw new RuntimeException('Field generator polynomial is not primitive'); } // Form RS code generator polynomial from its roots $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false); $this->firstRoot = $firstRoot; $this->primitive = $primitive; $this->numRoots = $numRoots; // Find prim-th root of 1, used in decoding for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) { } $this->iPrimitive = intdiv($iPrimitive, $primitive); $this->generatorPoly[0] = 1; for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) { $this->generatorPoly[$i + 1] = 1; for ($j = $i; $j > 0; $j--) { if ($this->generatorPoly[$j] !== 0) { $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[ $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root) ]; } else { $this->generatorPoly[$j] = $this->generatorPoly[$j - 1]; } } $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)]; } // Convert generator poly to index form for quicker encoding for ($i = 0; $i <= $numRoots; ++$i) { $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]]; } } /** * Encodes data and writes result back into parity array. */ public function encode(SplFixedArray $data, SplFixedArray $parity) : void { for ($i = 0; $i < $this->numRoots; ++$i) { $parity[$i] = 0; } $iterations = $this->blockSize - $this->numRoots - $this->padding; for ($i = 0; $i < $iterations; ++$i) { $feedback = $this->indexOf[$data[$i] ^ $parity[0]]; if ($feedback !== $this->blockSize) { // Feedback term is non-zero $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback); for ($j = 1; $j < $this->numRoots; ++$j) { $parity[$j] = $parity[$j] ^ $this->alphaTo[ $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j]) ]; } } for ($j = 0; $j < $this->numRoots - 1; ++$j) { $parity[$j] = $parity[$j + 1]; } if ($feedback !== $this->blockSize) { $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])]; } else { $parity[$this->numRoots - 1] = 0; } } } /** * Decodes received data. */ public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int { // This speeds up the initialization a bit. $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false); $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false); $lambda = clone $numRootsPlusOne; $b = clone $numRootsPlusOne; $t = clone $numRootsPlusOne; $omega = clone $numRootsPlusOne; $root = clone $numRoots; $loc = clone $numRoots; $numErasures = (null !== $erasures ? count($erasures) : 0); // Form the Syndromes; i.e., evaluate data(x) at roots of g(x) $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false); for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) { for ($j = 0; $j < $this->numRoots; ++$j) { if ($syndromes[$j] === 0) { $syndromes[$j] = $data[$i]; } else { $syndromes[$j] = $data[$i] ^ $this->alphaTo[ $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive) ]; } } } // Convert syndromes to index form, checking for nonzero conditions $syndromeError = 0; for ($i = 0; $i < $this->numRoots; ++$i) { $syndromeError |= $syndromes[$i]; $syndromes[$i] = $this->indexOf[$syndromes[$i]]; } if (! $syndromeError) { // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[] // unmodified. return 0; } $lambda[0] = 1; if ($numErasures > 0) { // Init lambda to be the erasure locator polynomial $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))]; for ($i = 1; $i < $numErasures; ++$i) { $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i])); for ($j = $i + 1; $j > 0; --$j) { $tmp = $this->indexOf[$lambda[$j - 1]]; if ($tmp !== $this->blockSize) { $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)]; } } } } for ($i = 0; $i <= $this->numRoots; ++$i) { $b[$i] = $this->indexOf[$lambda[$i]]; } // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial $r = $numErasures; $el = $numErasures; while (++$r <= $this->numRoots) { // Compute discrepancy at the r-th step in poly form $discrepancyR = 0; for ($i = 0; $i < $r; ++$i) { if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) { $discrepancyR ^= $this->alphaTo[ $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1]) ]; } } $discrepancyR = $this->indexOf[$discrepancyR]; if ($discrepancyR === $this->blockSize) { $tmp = $b->toArray(); array_unshift($tmp, $this->blockSize); array_pop($tmp); $b = SplFixedArray::fromArray($tmp, false); continue; } $t[0] = $lambda[0]; for ($i = 0; $i < $this->numRoots; ++$i) { if ($b[$i] !== $this->blockSize) { $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])]; } else { $t[$i + 1] = $lambda[$i + 1]; } } if (2 * $el <= $r + $numErasures - 1) { $el = $r + $numErasures - $el; for ($i = 0; $i <= $this->numRoots; ++$i) { $b[$i] = ( $lambda[$i] === 0 ? $this->blockSize : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize) ); } } else { $tmp = $b->toArray(); array_unshift($tmp, $this->blockSize); array_pop($tmp); $b = SplFixedArray::fromArray($tmp, false); } $lambda = clone $t; } // Convert lambda to index form and compute deg(lambda(x)) $degLambda = 0; for ($i = 0; $i <= $this->numRoots; ++$i) { $lambda[$i] = $this->indexOf[$lambda[$i]]; if ($lambda[$i] !== $this->blockSize) { $degLambda = $i; } } // Find roots of the error+erasure locator polynomial by Chien search. $reg = clone $lambda; $reg[0] = 0; $count = 0; $i = 1; for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) { $q = 1; for ($j = $degLambda; $j > 0; $j--) { if ($reg[$j] !== $this->blockSize) { $reg[$j] = $this->modNn($reg[$j] + $j); $q ^= $this->alphaTo[$reg[$j]]; } } if ($q !== 0) { // Not a root continue; } // Store root (index-form) and error location number $root[$count] = $i; $loc[$count] = $k; if (++$count === $degLambda) { break; } } if ($degLambda !== $count) { // deg(lambda) unequal to number of roots: uncorrectable error detected return null; } // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find // deg(omega). $degOmega = $degLambda - 1; for ($i = 0; $i <= $degOmega; ++$i) { $tmp = 0; for ($j = $i; $j >= 0; --$j) { if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) { $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])]; } } $omega[$i] = $this->indexOf[$tmp]; } // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and // den = lambda_pr(inv(X(l))) all in poly form. for ($j = $count - 1; $j >= 0; --$j) { $num1 = 0; for ($i = $degOmega; $i >= 0; $i--) { if ($omega[$i] !== $this->blockSize) { $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])]; } } $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)]; $den = 0; // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i] for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) { if ($lambda[$i + 1] !== $this->blockSize) { $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])]; } } // Apply error to data if ($num1 !== 0 && $loc[$j] >= $this->padding) { $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ ( $this->alphaTo[ $this->modNn( $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den] ) ] ); } } if (null !== $erasures) { if (count($erasures) < $count) { $erasures->setSize($count); } for ($i = 0; $i < $count; $i++) { $erasures[$i] = $loc[$i]; } } return $count; } /** * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide. */ private function modNn(int $x) : int { while ($x >= $this->blockSize) { $x -= $this->blockSize; $x = ($x >> $this->symbolSize) + ($x & $this->blockSize); } return $x; } } src/Common/Mode.php000077700000003774151323635260010210 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; use DASPRiD\Enum\AbstractEnum; /** * Enum representing various modes in which data can be encoded to bits. * * @method static self TERMINATOR() * @method static self NUMERIC() * @method static self ALPHANUMERIC() * @method static self STRUCTURED_APPEND() * @method static self BYTE() * @method static self ECI() * @method static self KANJI() * @method static self FNC1_FIRST_POSITION() * @method static self FNC1_SECOND_POSITION() * @method static self HANZI() */ final class Mode extends AbstractEnum { protected const TERMINATOR = [[0, 0, 0], 0x00]; protected const NUMERIC = [[10, 12, 14], 0x01]; protected const ALPHANUMERIC = [[9, 11, 13], 0x02]; protected const STRUCTURED_APPEND = [[0, 0, 0], 0x03]; protected const BYTE = [[8, 16, 16], 0x04]; protected const ECI = [[0, 0, 0], 0x07]; protected const KANJI = [[8, 10, 12], 0x08]; protected const FNC1_FIRST_POSITION = [[0, 0, 0], 0x05]; protected const FNC1_SECOND_POSITION = [[0, 0, 0], 0x09]; protected const HANZI = [[8, 10, 12], 0x0d]; /** * @var int[] */ private $characterCountBitsForVersions; /** * @var int */ private $bits; protected function __construct(array $characterCountBitsForVersions, int $bits) { $this->characterCountBitsForVersions = $characterCountBitsForVersions; $this->bits = $bits; } /** * Returns the number of bits used in a specific QR code version. */ public function getCharacterCountBits(Version $version) : int { $number = $version->getVersionNumber(); if ($number <= 9) { $offset = 0; } elseif ($number <= 26) { $offset = 1; } else { $offset = 2; } return $this->characterCountBitsForVersions[$offset]; } /** * Returns the four bits used to encode this mode. */ public function getBits() : int { return $this->bits; } } src/Common/Version.php000077700000052560151323635260010746 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; use BaconQrCode\Exception\InvalidArgumentException; use SplFixedArray; /** * Version representation. */ final class Version { private const VERSION_DECODE_INFO = [ 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, 0x27541, 0x28c69, ]; /** * Version number of this version. * * @var int */ private $versionNumber; /** * Alignment pattern centers. * * @var SplFixedArray */ private $alignmentPatternCenters; /** * Error correction blocks. * * @var EcBlocks[] */ private $ecBlocks; /** * Total number of codewords. * * @var int */ private $totalCodewords; /** * Cached version instances. * * @var array<int, self>|null */ private static $versions; /** * @param int[] $alignmentPatternCenters */ private function __construct( int $versionNumber, array $alignmentPatternCenters, EcBlocks ...$ecBlocks ) { $this->versionNumber = $versionNumber; $this->alignmentPatternCenters = $alignmentPatternCenters; $this->ecBlocks = $ecBlocks; $totalCodewords = 0; $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock(); foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) { $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords); } $this->totalCodewords = $totalCodewords; } /** * Returns the version number. */ public function getVersionNumber() : int { return $this->versionNumber; } /** * Returns the alignment pattern centers. * * @return int[] */ public function getAlignmentPatternCenters() : array { return $this->alignmentPatternCenters; } /** * Returns the total number of codewords. */ public function getTotalCodewords() : int { return $this->totalCodewords; } /** * Calculates the dimension for the current version. */ public function getDimensionForVersion() : int { return 17 + 4 * $this->versionNumber; } /** * Returns the number of EC blocks for a specific EC level. */ public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks { return $this->ecBlocks[$ecLevel->ordinal()]; } /** * Gets a provisional version number for a specific dimension. * * @throws InvalidArgumentException if dimension is not 1 mod 4 */ public static function getProvisionalVersionForDimension(int $dimension) : self { if (1 !== $dimension % 4) { throw new InvalidArgumentException('Dimension is not 1 mod 4'); } return self::getVersionForNumber(intdiv($dimension - 17, 4)); } /** * Gets a version instance for a specific version number. * * @throws InvalidArgumentException if version number is out of range */ public static function getVersionForNumber(int $versionNumber) : self { if ($versionNumber < 1 || $versionNumber > 40) { throw new InvalidArgumentException('Version number must be between 1 and 40'); } return self::versions()[$versionNumber - 1]; } /** * Decodes version information from an integer and returns the version. */ public static function decodeVersionInformation(int $versionBits) : ?self { $bestDifference = PHP_INT_MAX; $bestVersion = 0; foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) { if ($targetVersion === $versionBits) { return self::getVersionForNumber($i + 7); } $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion); if ($bitsDifference < $bestDifference) { $bestVersion = $i + 7; $bestDifference = $bitsDifference; } } if ($bestDifference <= 3) { return self::getVersionForNumber($bestVersion); } return null; } /** * Builds the function pattern for the current version. */ public function buildFunctionPattern() : BitMatrix { $dimension = $this->getDimensionForVersion(); $bitMatrix = new BitMatrix($dimension); // Top left finder pattern + separator + format $bitMatrix->setRegion(0, 0, 9, 9); // Top right finder pattern + separator + format $bitMatrix->setRegion($dimension - 8, 0, 8, 9); // Bottom left finder pattern + separator + format $bitMatrix->setRegion(0, $dimension - 8, 9, 8); // Alignment patterns $max = count($this->alignmentPatternCenters); for ($x = 0; $x < $max; ++$x) { $i = $this->alignmentPatternCenters[$x] - 2; for ($y = 0; $y < $max; ++$y) { if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) { // No alignment patterns near the three finder paterns continue; } $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5); } } // Vertical timing pattern $bitMatrix->setRegion(6, 9, 1, $dimension - 17); // Horizontal timing pattern $bitMatrix->setRegion(9, 6, $dimension - 17, 1); if ($this->versionNumber > 6) { // Version info, top right $bitMatrix->setRegion($dimension - 11, 0, 3, 6); // Version info, bottom left $bitMatrix->setRegion(0, $dimension - 11, 6, 3); } return $bitMatrix; } /** * Returns a string representation for the version. */ public function __toString() : string { return (string) $this->versionNumber; } /** * Build and cache a specific version. * * See ISO 18004:2006 6.5.1 Table 9. * * @return array<int, self> */ private static function versions() : array { if (null !== self::$versions) { return self::$versions; } return self::$versions = [ new self( 1, [], new EcBlocks(7, new EcBlock(1, 19)), new EcBlocks(10, new EcBlock(1, 16)), new EcBlocks(13, new EcBlock(1, 13)), new EcBlocks(17, new EcBlock(1, 9)) ), new self( 2, [6, 18], new EcBlocks(10, new EcBlock(1, 34)), new EcBlocks(16, new EcBlock(1, 28)), new EcBlocks(22, new EcBlock(1, 22)), new EcBlocks(28, new EcBlock(1, 16)) ), new self( 3, [6, 22], new EcBlocks(15, new EcBlock(1, 55)), new EcBlocks(26, new EcBlock(1, 44)), new EcBlocks(18, new EcBlock(2, 17)), new EcBlocks(22, new EcBlock(2, 13)) ), new self( 4, [6, 26], new EcBlocks(20, new EcBlock(1, 80)), new EcBlocks(18, new EcBlock(2, 32)), new EcBlocks(26, new EcBlock(3, 24)), new EcBlocks(16, new EcBlock(4, 9)) ), new self( 5, [6, 30], new EcBlocks(26, new EcBlock(1, 108)), new EcBlocks(24, new EcBlock(2, 43)), new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)), new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12)) ), new self( 6, [6, 34], new EcBlocks(18, new EcBlock(2, 68)), new EcBlocks(16, new EcBlock(4, 27)), new EcBlocks(24, new EcBlock(4, 19)), new EcBlocks(28, new EcBlock(4, 15)) ), new self( 7, [6, 22, 38], new EcBlocks(20, new EcBlock(2, 78)), new EcBlocks(18, new EcBlock(4, 31)), new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)), new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14)) ), new self( 8, [6, 24, 42], new EcBlocks(24, new EcBlock(2, 97)), new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)), new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)), new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15)) ), new self( 9, [6, 26, 46], new EcBlocks(30, new EcBlock(2, 116)), new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)), new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)), new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13)) ), new self( 10, [6, 28, 50], new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)), new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)), new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)), new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16)) ), new self( 11, [6, 30, 54], new EcBlocks(20, new EcBlock(4, 81)), new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)), new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)), new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13)) ), new self( 12, [6, 32, 58], new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)), new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)), new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)), new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15)) ), new self( 13, [6, 34, 62], new EcBlocks(26, new EcBlock(4, 107)), new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)), new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)), new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12)) ), new self( 14, [6, 26, 46, 66], new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)), new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)), new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)), new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13)) ), new self( 15, [6, 26, 48, 70], new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)), new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)), new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)), new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13)) ), new self( 16, [6, 26, 50, 74], new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)), new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)), new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)), new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16)) ), new self( 17, [6, 30, 54, 78], new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)), new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)), new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)), new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15)) ), new self( 18, [6, 30, 56, 82], new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)), new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)), new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)), new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15)) ), new self( 19, [6, 30, 58, 86], new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)), new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)), new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)), new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14)) ), new self( 20, [6, 34, 62, 90], new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)), new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)), new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)), new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16)) ), new self( 21, [6, 28, 50, 72, 94], new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)), new EcBlocks(26, new EcBlock(17, 42)), new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)), new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17)) ), new self( 22, [6, 26, 50, 74, 98], new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)), new EcBlocks(28, new EcBlock(17, 46)), new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)), new EcBlocks(24, new EcBlock(34, 13)) ), new self( 23, [6, 30, 54, 78, 102], new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)), new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)), new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)), new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16)) ), new self( 24, [6, 28, 54, 80, 106], new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)), new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)), new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)), new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17)) ), new self( 25, [6, 32, 58, 84, 110], new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)), new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)), new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)), new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16)) ), new self( 26, [6, 30, 58, 86, 114], new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)), new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)), new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)), new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17)) ), new self( 27, [6, 34, 62, 90, 118], new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)), new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)), new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)), new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16)) ), new self( 28, [6, 26, 50, 74, 98, 122], new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)), new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)), new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)), new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16)) ), new self( 29, [6, 30, 54, 78, 102, 126], new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)), new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)), new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)), new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16)) ), new self( 30, [6, 26, 52, 78, 104, 130], new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)), new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)), new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)), new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16)) ), new self( 31, [6, 30, 56, 82, 108, 134], new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)), new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)), new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)), new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16)) ), new self( 32, [6, 34, 60, 86, 112, 138], new EcBlocks(30, new EcBlock(17, 115)), new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)), new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)), new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16)) ), new self( 33, [6, 30, 58, 86, 114, 142], new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)), new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)), new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)), new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16)) ), new self( 34, [6, 34, 62, 90, 118, 146], new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)), new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)), new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)), new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17)) ), new self( 35, [6, 30, 54, 78, 102, 126, 150], new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)), new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)), new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)), new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16)) ), new self( 36, [6, 24, 50, 76, 102, 128, 154], new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)), new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)), new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)), new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16)) ), new self( 37, [6, 28, 54, 80, 106, 132, 158], new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)), new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)), new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)), new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16)) ), new self( 38, [6, 32, 58, 84, 110, 136, 162], new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)), new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)), new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)), new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16)) ), new self( 39, [6, 26, 54, 82, 110, 138, 166], new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)), new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)), new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)), new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16)) ), new self( 40, [6, 30, 58, 86, 114, 142, 170], new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)), new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)), new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)), new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16)) ), ]; } } src/Common/FormatInformation.php000077700000013242151323635260012751 0ustar00<?php /** * BaconQrCode * * @link http://github.com/Bacon/BaconQrCode For the canonical source repository * @copyright 2013 Ben 'DASPRiD' Scholzen * @license http://opensource.org/licenses/BSD-2-Clause Simplified BSD License */ namespace BaconQrCode\Common; /** * Encapsulates a QR Code's format information, including the data mask used and error correction level. */ class FormatInformation { /** * Mask for format information. */ private const FORMAT_INFO_MASK_QR = 0x5412; /** * Lookup table for decoding format information. * * See ISO 18004:2006, Annex C, Table C.1 */ private const FORMAT_INFO_DECODE_LOOKUP = [ [0x5412, 0x00], [0x5125, 0x01], [0x5e7c, 0x02], [0x5b4b, 0x03], [0x45f9, 0x04], [0x40ce, 0x05], [0x4f97, 0x06], [0x4aa0, 0x07], [0x77c4, 0x08], [0x72f3, 0x09], [0x7daa, 0x0a], [0x789d, 0x0b], [0x662f, 0x0c], [0x6318, 0x0d], [0x6c41, 0x0e], [0x6976, 0x0f], [0x1689, 0x10], [0x13be, 0x11], [0x1ce7, 0x12], [0x19d0, 0x13], [0x0762, 0x14], [0x0255, 0x15], [0x0d0c, 0x16], [0x083b, 0x17], [0x355f, 0x18], [0x3068, 0x19], [0x3f31, 0x1a], [0x3a06, 0x1b], [0x24b4, 0x1c], [0x2183, 0x1d], [0x2eda, 0x1e], [0x2bed, 0x1f], ]; /** * Offset i holds the number of 1 bits in the binary representation of i. * * @var array */ private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4]; /** * Error correction level. * * @var ErrorCorrectionLevel */ private $ecLevel; /** * Data mask. * * @var int */ private $dataMask; protected function __construct(int $formatInfo) { $this->ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3); $this->dataMask = $formatInfo & 0x7; } /** * Checks how many bits are different between two integers. */ public static function numBitsDiffering(int $a, int $b) : int { $a ^= $b; return ( self::BITS_SET_IN_HALF_BYTE[$a & 0xf] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)] + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)] ); } /** * Decodes format information. */ public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self { $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2); if (null !== $formatInfo) { return $formatInfo; } // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the // pattern first. return self::doDecodeFormatInformation( $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR, $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR ); } /** * Internal method for decoding format information. */ private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self { $bestDifference = PHP_INT_MAX; $bestFormatInfo = 0; foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) { $targetInfo = $decodeInfo[0]; if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) { // Found an exact match return new self($decodeInfo[1]); } $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo); if ($bitsDifference < $bestDifference) { $bestFormatInfo = $decodeInfo[1]; $bestDifference = $bitsDifference; } if ($maskedFormatInfo1 !== $maskedFormatInfo2) { // Also try the other option $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo); if ($bitsDifference < $bestDifference) { $bestFormatInfo = $decodeInfo[1]; $bestDifference = $bitsDifference; } } } // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match. if ($bestDifference <= 3) { return new self($bestFormatInfo); } return null; } /** * Returns the error correction level. */ public function getErrorCorrectionLevel() : ErrorCorrectionLevel { return $this->ecLevel; } /** * Returns the data mask. */ public function getDataMask() : int { return $this->dataMask; } /** * Hashes the code of the EC level. */ public function hashCode() : int { return ($this->ecLevel->getBits() << 3) | $this->dataMask; } /** * Verifies if this instance equals another one. */ public function equals(self $other) : bool { return ( $this->ecLevel === $other->ecLevel && $this->dataMask === $other->dataMask ); } } src/Common/CharacterSetEci.php000077700000012270151323635260012304 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; use BaconQrCode\Exception\InvalidArgumentException; use DASPRiD\Enum\AbstractEnum; /** * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004. * * @method static self CP437() * @method static self ISO8859_1() * @method static self ISO8859_2() * @method static self ISO8859_3() * @method static self ISO8859_4() * @method static self ISO8859_5() * @method static self ISO8859_6() * @method static self ISO8859_7() * @method static self ISO8859_8() * @method static self ISO8859_9() * @method static self ISO8859_10() * @method static self ISO8859_11() * @method static self ISO8859_12() * @method static self ISO8859_13() * @method static self ISO8859_14() * @method static self ISO8859_15() * @method static self ISO8859_16() * @method static self SJIS() * @method static self CP1250() * @method static self CP1251() * @method static self CP1252() * @method static self CP1256() * @method static self UNICODE_BIG_UNMARKED() * @method static self UTF8() * @method static self ASCII() * @method static self BIG5() * @method static self GB18030() * @method static self EUC_KR() */ final class CharacterSetEci extends AbstractEnum { protected const CP437 = [[0, 2]]; protected const ISO8859_1 = [[1, 3], 'ISO-8859-1']; protected const ISO8859_2 = [[4], 'ISO-8859-2']; protected const ISO8859_3 = [[5], 'ISO-8859-3']; protected const ISO8859_4 = [[6], 'ISO-8859-4']; protected const ISO8859_5 = [[7], 'ISO-8859-5']; protected const ISO8859_6 = [[8], 'ISO-8859-6']; protected const ISO8859_7 = [[9], 'ISO-8859-7']; protected const ISO8859_8 = [[10], 'ISO-8859-8']; protected const ISO8859_9 = [[11], 'ISO-8859-9']; protected const ISO8859_10 = [[12], 'ISO-8859-10']; protected const ISO8859_11 = [[13], 'ISO-8859-11']; protected const ISO8859_12 = [[14], 'ISO-8859-12']; protected const ISO8859_13 = [[15], 'ISO-8859-13']; protected const ISO8859_14 = [[16], 'ISO-8859-14']; protected const ISO8859_15 = [[17], 'ISO-8859-15']; protected const ISO8859_16 = [[18], 'ISO-8859-16']; protected const SJIS = [[20], 'Shift_JIS']; protected const CP1250 = [[21], 'windows-1250']; protected const CP1251 = [[22], 'windows-1251']; protected const CP1252 = [[23], 'windows-1252']; protected const CP1256 = [[24], 'windows-1256']; protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig']; protected const UTF8 = [[26], 'UTF-8']; protected const ASCII = [[27, 170], 'US-ASCII']; protected const BIG5 = [[28]]; protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK']; protected const EUC_KR = [[30], 'EUC-KR']; /** * @var int[] */ private $values; /** * @var string[] */ private $otherEncodingNames; /** * @var array<int, self>|null */ private static $valueToEci; /** * @var array<string, self>|null */ private static $nameToEci; public function __construct(array $values, string ...$otherEncodingNames) { $this->values = $values; $this->otherEncodingNames = $otherEncodingNames; } /** * Returns the primary value. */ public function getValue() : int { return $this->values[0]; } /** * Gets character set ECI by value. * * Returns the representing ECI of a given value, or null if it is legal but unsupported. * * @throws InvalidArgumentException if value is not between 0 and 900 */ public static function getCharacterSetEciByValue(int $value) : ?self { if ($value < 0 || $value >= 900) { throw new InvalidArgumentException('Value must be between 0 and 900'); } $valueToEci = self::valueToEci(); if (! array_key_exists($value, $valueToEci)) { return null; } return $valueToEci[$value]; } /** * Returns character set ECI by name. * * Returns the representing ECI of a given name, or null if it is legal but unsupported */ public static function getCharacterSetEciByName(string $name) : ?self { $nameToEci = self::nameToEci(); $name = strtolower($name); if (! array_key_exists($name, $nameToEci)) { return null; } return $nameToEci[$name]; } private static function valueToEci() : array { if (null !== self::$valueToEci) { return self::$valueToEci; } self::$valueToEci = []; foreach (self::values() as $eci) { foreach ($eci->values as $value) { self::$valueToEci[$value] = $eci; } } return self::$valueToEci; } private static function nameToEci() : array { if (null !== self::$nameToEci) { return self::$nameToEci; } self::$nameToEci = []; foreach (self::values() as $eci) { self::$nameToEci[strtolower($eci->name())] = $eci; foreach ($eci->otherEncodingNames as $name) { self::$nameToEci[strtolower($name)] = $eci; } } return self::$nameToEci; } } src/Common/BitMatrix.php000077700000017070151323635260011221 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; use BaconQrCode\Exception\InvalidArgumentException; use SplFixedArray; /** * Bit matrix. * * Represents a 2D matrix of bits. In function arguments below, and throughout * the common module, x is the column position, and y is the row position. The * ordering is always x, y. The origin is at the top-left. */ class BitMatrix { /** * Width of the bit matrix. * * @var int */ private $width; /** * Height of the bit matrix. * * @var int */ private $height; /** * Size in bits of each individual row. * * @var int */ private $rowSize; /** * Bits representation. * * @var SplFixedArray<int> */ private $bits; /** * @throws InvalidArgumentException if a dimension is smaller than zero */ public function __construct(int $width, int $height = null) { if (null === $height) { $height = $width; } if ($width < 1 || $height < 1) { throw new InvalidArgumentException('Both dimensions must be greater than zero'); } $this->width = $width; $this->height = $height; $this->rowSize = ($width + 31) >> 5; $this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0)); } /** * Gets the requested bit, where true means black. */ public function get(int $x, int $y) : bool { $offset = $y * $this->rowSize + ($x >> 5); return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1); } /** * Sets the given bit to true. */ public function set(int $x, int $y) : void { $offset = $y * $this->rowSize + ($x >> 5); $this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f)); } /** * Flips the given bit. */ public function flip(int $x, int $y) : void { $offset = $y * $this->rowSize + ($x >> 5); $this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f)); } /** * Clears all bits (set to false). */ public function clear() : void { $max = count($this->bits); for ($i = 0; $i < $max; ++$i) { $this->bits[$i] = 0; } } /** * Sets a square region of the bit matrix to true. * * @throws InvalidArgumentException if left or top are negative * @throws InvalidArgumentException if width or height are smaller than 1 * @throws InvalidArgumentException if region does not fit into the matix */ public function setRegion(int $left, int $top, int $width, int $height) : void { if ($top < 0 || $left < 0) { throw new InvalidArgumentException('Left and top must be non-negative'); } if ($height < 1 || $width < 1) { throw new InvalidArgumentException('Width and height must be at least 1'); } $right = $left + $width; $bottom = $top + $height; if ($bottom > $this->height || $right > $this->width) { throw new InvalidArgumentException('The region must fit inside the matrix'); } for ($y = $top; $y < $bottom; ++$y) { $offset = $y * $this->rowSize; for ($x = $left; $x < $right; ++$x) { $index = $offset + ($x >> 5); $this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f)); } } } /** * A fast method to retrieve one row of data from the matrix as a BitArray. */ public function getRow(int $y, BitArray $row = null) : BitArray { if (null === $row || $row->getSize() < $this->width) { $row = new BitArray($this->width); } $offset = $y * $this->rowSize; for ($x = 0; $x < $this->rowSize; ++$x) { $row->setBulk($x << 5, $this->bits[$offset + $x]); } return $row; } /** * Sets a row of data from a BitArray. */ public function setRow(int $y, BitArray $row) : void { $bits = $row->getBitArray(); for ($i = 0; $i < $this->rowSize; ++$i) { $this->bits[$y * $this->rowSize + $i] = $bits[$i]; } } /** * This is useful in detecting the enclosing rectangle of a 'pure' barcode. * * @return int[]|null */ public function getEnclosingRectangle() : ?array { $left = $this->width; $top = $this->height; $right = -1; $bottom = -1; for ($y = 0; $y < $this->height; ++$y) { for ($x32 = 0; $x32 < $this->rowSize; ++$x32) { $bits = $this->bits[$y * $this->rowSize + $x32]; if (0 !== $bits) { if ($y < $top) { $top = $y; } if ($y > $bottom) { $bottom = $y; } if ($x32 * 32 < $left) { $bit = 0; while (($bits << (31 - $bit)) === 0) { $bit++; } if (($x32 * 32 + $bit) < $left) { $left = $x32 * 32 + $bit; } } } if ($x32 * 32 + 31 > $right) { $bit = 31; while (0 === BitUtils::unsignedRightShift($bits, $bit)) { --$bit; } if (($x32 * 32 + $bit) > $right) { $right = $x32 * 32 + $bit; } } } } $width = $right - $left; $height = $bottom - $top; if ($width < 0 || $height < 0) { return null; } return [$left, $top, $width, $height]; } /** * Gets the most top left set bit. * * This is useful in detecting a corner of a 'pure' barcode. * * @return int[]|null */ public function getTopLeftOnBit() : ?array { $bitsOffset = 0; while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) { ++$bitsOffset; } if (count($this->bits) === $bitsOffset) { return null; } $x = intdiv($bitsOffset, $this->rowSize); $y = ($bitsOffset % $this->rowSize) << 5; $bits = $this->bits[$bitsOffset]; $bit = 0; while (0 === ($bits << (31 - $bit))) { ++$bit; } $x += $bit; return [$x, $y]; } /** * Gets the most bottom right set bit. * * This is useful in detecting a corner of a 'pure' barcode. * * @return int[]|null */ public function getBottomRightOnBit() : ?array { $bitsOffset = count($this->bits) - 1; while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) { --$bitsOffset; } if ($bitsOffset < 0) { return null; } $x = intdiv($bitsOffset, $this->rowSize); $y = ($bitsOffset % $this->rowSize) << 5; $bits = $this->bits[$bitsOffset]; $bit = 0; while (0 === BitUtils::unsignedRightShift($bits, $bit)) { --$bit; } $x += $bit; return [$x, $y]; } /** * Gets the width of the matrix, */ public function getWidth() : int { return $this->width; } /** * Gets the height of the matrix. */ public function getHeight() : int { return $this->height; } } src/Common/.htaccess000077700000000177151323635260010403 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Common/BitUtils.php000077700000001612151323635260011050 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Common; /** * General bit utilities. * * All utility methods are based on 32-bit integers and also work on 64-bit * systems. */ final class BitUtils { private function __construct() { } /** * Performs an unsigned right shift. * * This is the same as the unsigned right shift operator ">>>" in other * languages. */ public static function unsignedRightShift(int $a, int $b) : int { return ( $a >= 0 ? $a >> $b : (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1)) ); } /** * Gets the number of trailing zeros. */ public static function numberOfTrailingZeros(int $i) : int { $lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1'); return $lastPos === false ? 32 : 31 - $lastPos; } } src/Encoder/Encoder.php000077700000051517151323635260011030 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Encoder; use BaconQrCode\Common\BitArray; use BaconQrCode\Common\CharacterSetEci; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Common\Mode; use BaconQrCode\Common\ReedSolomonCodec; use BaconQrCode\Common\Version; use BaconQrCode\Exception\WriterException; use SplFixedArray; /** * Encoder. */ final class Encoder { /** * Default byte encoding. */ public const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1'; /** * The original table is defined in the table 5 of JISX0510:2004 (p.19). */ private const ALPHANUMERIC_TABLE = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f ]; /** * Codec cache. * * @var array */ private static $codecs = []; /** * Encodes "content" with the error correction level "ecLevel". */ public static function encode( string $content, ErrorCorrectionLevel $ecLevel, string $encoding = self::DEFAULT_BYTE_MODE_ECODING ) : QrCode { // Pick an encoding mode appropriate for the content. Note that this // will not attempt to use multiple modes / segments even if that were // more efficient. Would be nice. $mode = self::chooseMode($content, $encoding); // This will store the header information, like mode and length, as well // as "header" segments like an ECI segment. $headerBits = new BitArray(); // Append ECI segment if applicable if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) { $eci = CharacterSetEci::getCharacterSetEciByName($encoding); if (null !== $eci) { self::appendEci($eci, $headerBits); } } // (With ECI in place,) Write the mode marker self::appendModeInfo($mode, $headerBits); // Collect data within the main segment, separately, to count its size // if needed. Don't add it to main payload yet. $dataBits = new BitArray(); self::appendBytes($content, $mode, $dataBits, $encoding); // Hard part: need to know version to know how many bits length takes. // But need to know how many bits it takes to know version. First we // take a guess at version by assuming version will be the minimum, 1: $provisionalBitsNeeded = $headerBits->getSize() + $mode->getCharacterCountBits(Version::getVersionForNumber(1)) + $dataBits->getSize(); $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel); // Use that guess to calculate the right version. I am still not sure // this works in 100% of cases. $bitsNeeded = $headerBits->getSize() + $mode->getCharacterCountBits($provisionalVersion) + $dataBits->getSize(); $version = self::chooseVersion($bitsNeeded, $ecLevel); $headerAndDataBits = new BitArray(); $headerAndDataBits->appendBitArray($headerBits); // Find "length" of main segment and write it. $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content)); self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits); // Put data together into the overall payload. $headerAndDataBits->appendBitArray($dataBits); $ecBlocks = $version->getEcBlocksForLevel($ecLevel); $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords(); // Terminate the bits properly. self::terminateBits($numDataBytes, $headerAndDataBits); // Interleave data bits with error correction code. $finalBits = self::interleaveWithEcBytes( $headerAndDataBits, $version->getTotalCodewords(), $numDataBytes, $ecBlocks->getNumBlocks() ); // Choose the mask pattern. $dimension = $version->getDimensionForVersion(); $matrix = new ByteMatrix($dimension, $dimension); $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix); // Build the matrix. MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix); return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix); } /** * Gets the alphanumeric code for a byte. */ private static function getAlphanumericCode(int $code) : int { if (isset(self::ALPHANUMERIC_TABLE[$code])) { return self::ALPHANUMERIC_TABLE[$code]; } return -1; } /** * Chooses the best mode for a given content. */ private static function chooseMode(string $content, string $encoding = null) : Mode { if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) { return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE(); } $hasNumeric = false; $hasAlphanumeric = false; $contentLength = strlen($content); for ($i = 0; $i < $contentLength; ++$i) { $char = $content[$i]; if (ctype_digit($char)) { $hasNumeric = true; } elseif (-1 !== self::getAlphanumericCode(ord($char))) { $hasAlphanumeric = true; } else { return Mode::BYTE(); } } if ($hasAlphanumeric) { return Mode::ALPHANUMERIC(); } elseif ($hasNumeric) { return Mode::NUMERIC(); } return Mode::BYTE(); } /** * Calculates the mask penalty for a matrix. */ private static function calculateMaskPenalty(ByteMatrix $matrix) : int { return ( MaskUtil::applyMaskPenaltyRule1($matrix) + MaskUtil::applyMaskPenaltyRule2($matrix) + MaskUtil::applyMaskPenaltyRule3($matrix) + MaskUtil::applyMaskPenaltyRule4($matrix) ); } /** * Checks if content only consists of double-byte kanji characters. */ private static function isOnlyDoubleByteKanji(string $content) : bool { $bytes = @iconv('utf-8', 'SHIFT-JIS', $content); if (false === $bytes) { return false; } $length = strlen($bytes); if (0 !== $length % 2) { return false; } for ($i = 0; $i < $length; $i += 2) { $byte = $bytes[$i] & 0xff; if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) { return false; } } return true; } /** * Chooses the best mask pattern for a matrix. */ private static function chooseMaskPattern( BitArray $bits, ErrorCorrectionLevel $ecLevel, Version $version, ByteMatrix $matrix ) : int { $minPenalty = PHP_INT_MAX; $bestMaskPattern = -1; for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) { MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix); $penalty = self::calculateMaskPenalty($matrix); if ($penalty < $minPenalty) { $minPenalty = $penalty; $bestMaskPattern = $maskPattern; } } return $bestMaskPattern; } /** * Chooses the best version for the input. * * @throws WriterException if data is too big */ private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version { for ($versionNum = 1; $versionNum <= 40; ++$versionNum) { $version = Version::getVersionForNumber($versionNum); $numBytes = $version->getTotalCodewords(); $ecBlocks = $version->getEcBlocksForLevel($ecLevel); $numEcBytes = $ecBlocks->getTotalEcCodewords(); $numDataBytes = $numBytes - $numEcBytes; $totalInputBytes = intdiv($numInputBits + 8, 8); if ($numDataBytes >= $totalInputBytes) { return $version; } } throw new WriterException('Data too big'); } /** * Terminates the bits in a bit array. * * @throws WriterException if data bits cannot fit in the QR code * @throws WriterException if bits size does not equal the capacity */ private static function terminateBits(int $numDataBytes, BitArray $bits) : void { $capacity = $numDataBytes << 3; if ($bits->getSize() > $capacity) { throw new WriterException('Data bits cannot fit in the QR code'); } for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) { $bits->appendBit(false); } $numBitsInLastByte = $bits->getSize() & 0x7; if ($numBitsInLastByte > 0) { for ($i = $numBitsInLastByte; $i < 8; ++$i) { $bits->appendBit(false); } } $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes(); for ($i = 0; $i < $numPaddingBytes; ++$i) { $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8); } if ($bits->getSize() !== $capacity) { throw new WriterException('Bits size does not equal capacity'); } } /** * Gets number of data- and EC bytes for a block ID. * * @return int[] * @throws WriterException if block ID is too large * @throws WriterException if EC bytes mismatch * @throws WriterException if RS blocks mismatch * @throws WriterException if total bytes mismatch */ private static function getNumDataBytesAndNumEcBytesForBlockId( int $numTotalBytes, int $numDataBytes, int $numRsBlocks, int $blockId ) : array { if ($blockId >= $numRsBlocks) { throw new WriterException('Block ID too large'); } $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks; $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2; $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks); $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1; $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks); $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1; $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1; $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2; if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) { throw new WriterException('EC bytes mismatch'); } if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) { throw new WriterException('RS blocks mismatch'); } if ($numTotalBytes !== (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1) + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2) ) { throw new WriterException('Total bytes mismatch'); } if ($blockId < $numRsBlocksInGroup1) { return [$numDataBytesInGroup1, $numEcBytesInGroup1]; } else { return [$numDataBytesInGroup2, $numEcBytesInGroup2]; } } /** * Interleaves data with EC bytes. * * @throws WriterException if number of bits and data bytes does not match * @throws WriterException if data bytes does not match offset * @throws WriterException if an interleaving error occurs */ private static function interleaveWithEcBytes( BitArray $bits, int $numTotalBytes, int $numDataBytes, int $numRsBlocks ) : BitArray { if ($bits->getSizeInBytes() !== $numDataBytes) { throw new WriterException('Number of bits and data bytes does not match'); } $dataBytesOffset = 0; $maxNumDataBytes = 0; $maxNumEcBytes = 0; $blocks = new SplFixedArray($numRsBlocks); for ($i = 0; $i < $numRsBlocks; ++$i) { list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId( $numTotalBytes, $numDataBytes, $numRsBlocks, $i ); $size = $numDataBytesInBlock; $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size); $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock); $blocks[$i] = new BlockPair($dataBytes, $ecBytes); $maxNumDataBytes = max($maxNumDataBytes, $size); $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes)); $dataBytesOffset += $numDataBytesInBlock; } if ($numDataBytes !== $dataBytesOffset) { throw new WriterException('Data bytes does not match offset'); } $result = new BitArray(); for ($i = 0; $i < $maxNumDataBytes; ++$i) { foreach ($blocks as $block) { $dataBytes = $block->getDataBytes(); if ($i < count($dataBytes)) { $result->appendBits($dataBytes[$i], 8); } } } for ($i = 0; $i < $maxNumEcBytes; ++$i) { foreach ($blocks as $block) { $ecBytes = $block->getErrorCorrectionBytes(); if ($i < count($ecBytes)) { $result->appendBits($ecBytes[$i], 8); } } } if ($numTotalBytes !== $result->getSizeInBytes()) { throw new WriterException( 'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ' ); } return $result; } /** * Generates EC bytes for given data. * * @param SplFixedArray<int> $dataBytes * @return SplFixedArray<int> */ private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray { $numDataBytes = count($dataBytes); $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock); for ($i = 0; $i < $numDataBytes; $i++) { $toEncode[$i] = $dataBytes[$i] & 0xff; } $ecBytes = new SplFixedArray($numEcBytesInBlock); $codec = self::getCodec($numDataBytes, $numEcBytesInBlock); $codec->encode($toEncode, $ecBytes); return $ecBytes; } /** * Gets an RS codec and caches it. */ private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec { $cacheId = $numDataBytes . '-' . $numEcBytesInBlock; if (isset(self::$codecs[$cacheId])) { return self::$codecs[$cacheId]; } return self::$codecs[$cacheId] = new ReedSolomonCodec( 8, 0x11d, 0, 1, $numEcBytesInBlock, 255 - $numDataBytes - $numEcBytesInBlock ); } /** * Appends mode information to a bit array. */ private static function appendModeInfo(Mode $mode, BitArray $bits) : void { $bits->appendBits($mode->getBits(), 4); } /** * Appends length information to a bit array. * * @throws WriterException if num letters is bigger than expected */ private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void { $numBits = $mode->getCharacterCountBits($version); if ($numLetters >= (1 << $numBits)) { throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1)); } $bits->appendBits($numLetters, $numBits); } /** * Appends bytes to a bit array in a specific mode. * * @throws WriterException if an invalid mode was supplied */ private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void { switch ($mode) { case Mode::NUMERIC(): self::appendNumericBytes($content, $bits); break; case Mode::ALPHANUMERIC(): self::appendAlphanumericBytes($content, $bits); break; case Mode::BYTE(): self::append8BitBytes($content, $bits, $encoding); break; case Mode::KANJI(): self::appendKanjiBytes($content, $bits); break; default: throw new WriterException('Invalid mode: ' . $mode); } } /** * Appends numeric bytes to a bit array. */ private static function appendNumericBytes(string $content, BitArray $bits) : void { $length = strlen($content); $i = 0; while ($i < $length) { $num1 = (int) $content[$i]; if ($i + 2 < $length) { // Encode three numeric letters in ten bits. $num2 = (int) $content[$i + 1]; $num3 = (int) $content[$i + 2]; $bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10); $i += 3; } elseif ($i + 1 < $length) { // Encode two numeric letters in seven bits. $num2 = (int) $content[$i + 1]; $bits->appendBits($num1 * 10 + $num2, 7); $i += 2; } else { // Encode one numeric letter in four bits. $bits->appendBits($num1, 4); ++$i; } } } /** * Appends alpha-numeric bytes to a bit array. * * @throws WriterException if an invalid alphanumeric code was found */ private static function appendAlphanumericBytes(string $content, BitArray $bits) : void { $length = strlen($content); $i = 0; while ($i < $length) { $code1 = self::getAlphanumericCode(ord($content[$i])); if (-1 === $code1) { throw new WriterException('Invalid alphanumeric code'); } if ($i + 1 < $length) { $code2 = self::getAlphanumericCode(ord($content[$i + 1])); if (-1 === $code2) { throw new WriterException('Invalid alphanumeric code'); } // Encode two alphanumeric letters in 11 bits. $bits->appendBits($code1 * 45 + $code2, 11); $i += 2; } else { // Encode one alphanumeric letter in six bits. $bits->appendBits($code1, 6); ++$i; } } } /** * Appends regular 8-bit bytes to a bit array. * * @throws WriterException if content cannot be encoded to target encoding */ private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void { $bytes = @iconv('utf-8', $encoding, $content); if (false === $bytes) { throw new WriterException('Could not encode content to ' . $encoding); } $length = strlen($bytes); for ($i = 0; $i < $length; $i++) { $bits->appendBits(ord($bytes[$i]), 8); } } /** * Appends KANJI bytes to a bit array. * * @throws WriterException if content does not seem to be encoded in SHIFT-JIS * @throws WriterException if an invalid byte sequence occurs */ private static function appendKanjiBytes(string $content, BitArray $bits) : void { if (strlen($content) % 2 > 0) { // We just do a simple length check here. The for loop will check // individual characters. throw new WriterException('Content does not seem to be encoded in SHIFT-JIS'); } $length = strlen($content); for ($i = 0; $i < $length; $i += 2) { $byte1 = ord($content[$i]) & 0xff; $byte2 = ord($content[$i + 1]) & 0xff; $code = ($byte1 << 8) | $byte2; if ($code >= 0x8140 && $code <= 0x9ffc) { $subtracted = $code - 0x8140; } elseif ($code >= 0xe040 && $code <= 0xebbf) { $subtracted = $code - 0xc140; } else { throw new WriterException('Invalid byte sequence'); } $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff); $bits->appendBits($encoded, 13); } } /** * Appends ECI information to a bit array. */ private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void { $mode = Mode::ECI(); $bits->appendBits($mode->getBits(), 4); $bits->appendBits($eci->getValue(), 8); } } src/Encoder/QrCode.php000077700000005324151323635260010621 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Encoder; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Common\Mode; use BaconQrCode\Common\Version; /** * QR code. */ final class QrCode { /** * Number of possible mask patterns. */ public const NUM_MASK_PATTERNS = 8; /** * Mode of the QR code. * * @var Mode */ private $mode; /** * EC level of the QR code. * * @var ErrorCorrectionLevel */ private $errorCorrectionLevel; /** * Version of the QR code. * * @var Version */ private $version; /** * Mask pattern of the QR code. * * @var int */ private $maskPattern = -1; /** * Matrix of the QR code. * * @var ByteMatrix */ private $matrix; public function __construct( Mode $mode, ErrorCorrectionLevel $errorCorrectionLevel, Version $version, int $maskPattern, ByteMatrix $matrix ) { $this->mode = $mode; $this->errorCorrectionLevel = $errorCorrectionLevel; $this->version = $version; $this->maskPattern = $maskPattern; $this->matrix = $matrix; } /** * Gets the mode. */ public function getMode() : Mode { return $this->mode; } /** * Gets the EC level. */ public function getErrorCorrectionLevel() : ErrorCorrectionLevel { return $this->errorCorrectionLevel; } /** * Gets the version. */ public function getVersion() : Version { return $this->version; } /** * Gets the mask pattern. */ public function getMaskPattern() : int { return $this->maskPattern; } /** * Gets the matrix. * * @return ByteMatrix */ public function getMatrix() { return $this->matrix; } /** * Validates whether a mask pattern is valid. */ public static function isValidMaskPattern(int $maskPattern) : bool { return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS; } /** * Returns a string representation of the QR code. */ public function __toString() : string { $result = "<<\n" . ' mode: ' . $this->mode . "\n" . ' ecLevel: ' . $this->errorCorrectionLevel . "\n" . ' version: ' . $this->version . "\n" . ' maskPattern: ' . $this->maskPattern . "\n"; if ($this->matrix === null) { $result .= " matrix: null\n"; } else { $result .= " matrix:\n"; $result .= $this->matrix; } $result .= ">>\n"; return $result; } } src/Encoder/MaskUtil.php000077700000020264151323635260011175 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Encoder; use BaconQrCode\Common\BitUtils; use BaconQrCode\Exception\InvalidArgumentException; /** * Mask utility. */ final class MaskUtil { /**#@+ * Penalty weights from section 6.8.2.1 */ const N1 = 3; const N2 = 3; const N3 = 40; const N4 = 10; /**#@-*/ private function __construct() { } /** * Applies mask penalty rule 1 and returns the penalty. * * Finds repetitive cells with the same color and gives penalty to them. * Example: 00000 or 11111. */ public static function applyMaskPenaltyRule1(ByteMatrix $matrix) : int { return ( self::applyMaskPenaltyRule1Internal($matrix, true) + self::applyMaskPenaltyRule1Internal($matrix, false) ); } /** * Applies mask penalty rule 2 and returns the penalty. * * Finds 2x2 blocks with the same color and gives penalty to them. This is * actually equivalent to the spec's rule, which is to find MxN blocks and * give a penalty proportional to (M-1)x(N-1), because this is the number of * 2x2 blocks inside such a block. */ public static function applyMaskPenaltyRule2(ByteMatrix $matrix) : int { $penalty = 0; $array = $matrix->getArray(); $width = $matrix->getWidth(); $height = $matrix->getHeight(); for ($y = 0; $y < $height - 1; ++$y) { for ($x = 0; $x < $width - 1; ++$x) { $value = $array[$y][$x]; if ($value === $array[$y][$x + 1] && $value === $array[$y + 1][$x] && $value === $array[$y + 1][$x + 1] ) { ++$penalty; } } } return self::N2 * $penalty; } /** * Applies mask penalty rule 3 and returns the penalty. * * Finds consecutive cells of 00001011101 or 10111010000, and gives penalty * to them. If we find patterns like 000010111010000, we give penalties * twice (i.e. 40 * 2). */ public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int { $penalty = 0; $array = $matrix->getArray(); $width = $matrix->getWidth(); $height = $matrix->getHeight(); for ($y = 0; $y < $height; ++$y) { for ($x = 0; $x < $width; ++$x) { if ($x + 6 < $width && 1 === $array[$y][$x] && 0 === $array[$y][$x + 1] && 1 === $array[$y][$x + 2] && 1 === $array[$y][$x + 3] && 1 === $array[$y][$x + 4] && 0 === $array[$y][$x + 5] && 1 === $array[$y][$x + 6] && ( ( $x + 10 < $width && 0 === $array[$y][$x + 7] && 0 === $array[$y][$x + 8] && 0 === $array[$y][$x + 9] && 0 === $array[$y][$x + 10] ) || ( $x - 4 >= 0 && 0 === $array[$y][$x - 1] && 0 === $array[$y][$x - 2] && 0 === $array[$y][$x - 3] && 0 === $array[$y][$x - 4] ) ) ) { $penalty += self::N3; } if ($y + 6 < $height && 1 === $array[$y][$x] && 0 === $array[$y + 1][$x] && 1 === $array[$y + 2][$x] && 1 === $array[$y + 3][$x] && 1 === $array[$y + 4][$x] && 0 === $array[$y + 5][$x] && 1 === $array[$y + 6][$x] && ( ( $y + 10 < $height && 0 === $array[$y + 7][$x] && 0 === $array[$y + 8][$x] && 0 === $array[$y + 9][$x] && 0 === $array[$y + 10][$x] ) || ( $y - 4 >= 0 && 0 === $array[$y - 1][$x] && 0 === $array[$y - 2][$x] && 0 === $array[$y - 3][$x] && 0 === $array[$y - 4][$x] ) ) ) { $penalty += self::N3; } } } return $penalty; } /** * Applies mask penalty rule 4 and returns the penalty. * * Calculates the ratio of dark cells and gives penalty if the ratio is far * from 50%. It gives 10 penalty for 5% distance. */ public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int { $numDarkCells = 0; $array = $matrix->getArray(); $width = $matrix->getWidth(); $height = $matrix->getHeight(); for ($y = 0; $y < $height; ++$y) { $arrayY = $array[$y]; for ($x = 0; $x < $width; ++$x) { if (1 === $arrayY[$x]) { ++$numDarkCells; } } } $numTotalCells = $height * $width; $darkRatio = $numDarkCells / $numTotalCells; $fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20); return $fixedPercentVariances * self::N4; } /** * Returns the mask bit for "getMaskPattern" at "x" and "y". * * See 8.8 of JISX0510:2004 for mask pattern conditions. * * @throws InvalidArgumentException if an invalid mask pattern was supplied */ public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool { switch ($maskPattern) { case 0: $intermediate = ($y + $x) & 0x1; break; case 1: $intermediate = $y & 0x1; break; case 2: $intermediate = $x % 3; break; case 3: $intermediate = ($y + $x) % 3; break; case 4: $intermediate = (BitUtils::unsignedRightShift($y, 1) + ($x / 3)) & 0x1; break; case 5: $temp = $y * $x; $intermediate = ($temp & 0x1) + ($temp % 3); break; case 6: $temp = $y * $x; $intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1; break; case 7: $temp = $y * $x; $intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1; break; default: throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern); } return 0 == $intermediate; } /** * Helper function for applyMaskPenaltyRule1. * * We need this for doing this calculation in both vertical and horizontal * orders respectively. */ private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int { $penalty = 0; $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth(); $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight(); $array = $matrix->getArray(); for ($i = 0; $i < $iLimit; ++$i) { $numSameBitCells = 0; $prevBit = -1; for ($j = 0; $j < $jLimit; $j++) { $bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i]; if ($bit === $prevBit) { ++$numSameBitCells; } else { if ($numSameBitCells >= 5) { $penalty += self::N1 + ($numSameBitCells - 5); } $numSameBitCells = 1; $prevBit = $bit; } } if ($numSameBitCells >= 5) { $penalty += self::N1 + ($numSameBitCells - 5); } } return $penalty; } } src/Encoder/BlockPair.php000077700000002126151323635260011307 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Encoder; use SplFixedArray; /** * Block pair. */ final class BlockPair { /** * Data bytes in the block. * * @var SplFixedArray<int> */ private $dataBytes; /** * Error correction bytes in the block. * * @var SplFixedArray<int> */ private $errorCorrectionBytes; /** * Creates a new block pair. * * @param SplFixedArray<int> $data * @param SplFixedArray<int> $errorCorrection */ public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection) { $this->dataBytes = $data; $this->errorCorrectionBytes = $errorCorrection; } /** * Gets the data bytes. * * @return SplFixedArray<int> */ public function getDataBytes() : SplFixedArray { return $this->dataBytes; } /** * Gets the error correction bytes. * * @return SplFixedArray<int> */ public function getErrorCorrectionBytes() : SplFixedArray { return $this->errorCorrectionBytes; } } src/Encoder/ByteMatrix.php000077700000005727151323635260011543 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Encoder; use SplFixedArray; use Traversable; /** * Byte matrix. */ final class ByteMatrix { /** * Bytes in the matrix, represented as array. * * @var SplFixedArray<SplFixedArray<int>> */ private $bytes; /** * Width of the matrix. * * @var int */ private $width; /** * Height of the matrix. * * @var int */ private $height; public function __construct(int $width, int $height) { $this->height = $height; $this->width = $width; $this->bytes = new SplFixedArray($height); for ($y = 0; $y < $height; ++$y) { $this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0)); } } /** * Gets the width of the matrix. */ public function getWidth() : int { return $this->width; } /** * Gets the height of the matrix. */ public function getHeight() : int { return $this->height; } /** * Gets the internal representation of the matrix. * * @return SplFixedArray<SplFixedArray<int>> */ public function getArray() : SplFixedArray { return $this->bytes; } /** * @return Traversable<int> */ public function getBytes() : Traversable { foreach ($this->bytes as $row) { foreach ($row as $byte) { yield $byte; } } } /** * Gets the byte for a specific position. */ public function get(int $x, int $y) : int { return $this->bytes[$y][$x]; } /** * Sets the byte for a specific position. */ public function set(int $x, int $y, int $value) : void { $this->bytes[$y][$x] = $value; } /** * Clears the matrix with a specific value. */ public function clear(int $value) : void { for ($y = 0; $y < $this->height; ++$y) { for ($x = 0; $x < $this->width; ++$x) { $this->bytes[$y][$x] = $value; } } } public function __clone() { $this->bytes = clone $this->bytes; foreach ($this->bytes as $index => $row) { $this->bytes[$index] = clone $row; } } /** * Returns a string representation of the matrix. */ public function __toString() : string { $result = ''; for ($y = 0; $y < $this->height; $y++) { for ($x = 0; $x < $this->width; $x++) { switch ($this->bytes[$y][$x]) { case 0: $result .= ' 0'; break; case 1: $result .= ' 1'; break; default: $result .= ' '; break; } } $result .= "\n"; } return $result; } } src/Encoder/MatrixUtil.php000077700000040752151323635260011552 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Encoder; use BaconQrCode\Common\BitArray; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Common\Version; use BaconQrCode\Exception\RuntimeException; use BaconQrCode\Exception\WriterException; /** * Matrix utility. */ final class MatrixUtil { /** * Position detection pattern. */ private const POSITION_DETECTION_PATTERN = [ [1, 1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0, 1], [1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1], [1, 0, 1, 1, 1, 0, 1], [1, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 1, 1], ]; /** * Position adjustment pattern. */ private const POSITION_ADJUSTMENT_PATTERN = [ [1, 1, 1, 1, 1], [1, 0, 0, 0, 1], [1, 0, 1, 0, 1], [1, 0, 0, 0, 1], [1, 1, 1, 1, 1], ]; /** * Coordinates for position adjustment patterns for each version. */ private const POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = [ [null, null, null, null, null, null, null], // Version 1 [ 6, 18, null, null, null, null, null], // Version 2 [ 6, 22, null, null, null, null, null], // Version 3 [ 6, 26, null, null, null, null, null], // Version 4 [ 6, 30, null, null, null, null, null], // Version 5 [ 6, 34, null, null, null, null, null], // Version 6 [ 6, 22, 38, null, null, null, null], // Version 7 [ 6, 24, 42, null, null, null, null], // Version 8 [ 6, 26, 46, null, null, null, null], // Version 9 [ 6, 28, 50, null, null, null, null], // Version 10 [ 6, 30, 54, null, null, null, null], // Version 11 [ 6, 32, 58, null, null, null, null], // Version 12 [ 6, 34, 62, null, null, null, null], // Version 13 [ 6, 26, 46, 66, null, null, null], // Version 14 [ 6, 26, 48, 70, null, null, null], // Version 15 [ 6, 26, 50, 74, null, null, null], // Version 16 [ 6, 30, 54, 78, null, null, null], // Version 17 [ 6, 30, 56, 82, null, null, null], // Version 18 [ 6, 30, 58, 86, null, null, null], // Version 19 [ 6, 34, 62, 90, null, null, null], // Version 20 [ 6, 28, 50, 72, 94, null, null], // Version 21 [ 6, 26, 50, 74, 98, null, null], // Version 22 [ 6, 30, 54, 78, 102, null, null], // Version 23 [ 6, 28, 54, 80, 106, null, null], // Version 24 [ 6, 32, 58, 84, 110, null, null], // Version 25 [ 6, 30, 58, 86, 114, null, null], // Version 26 [ 6, 34, 62, 90, 118, null, null], // Version 27 [ 6, 26, 50, 74, 98, 122, null], // Version 28 [ 6, 30, 54, 78, 102, 126, null], // Version 29 [ 6, 26, 52, 78, 104, 130, null], // Version 30 [ 6, 30, 56, 82, 108, 134, null], // Version 31 [ 6, 34, 60, 86, 112, 138, null], // Version 32 [ 6, 30, 58, 86, 114, 142, null], // Version 33 [ 6, 34, 62, 90, 118, 146, null], // Version 34 [ 6, 30, 54, 78, 102, 126, 150], // Version 35 [ 6, 24, 50, 76, 102, 128, 154], // Version 36 [ 6, 28, 54, 80, 106, 132, 158], // Version 37 [ 6, 32, 58, 84, 110, 136, 162], // Version 38 [ 6, 26, 54, 82, 110, 138, 166], // Version 39 [ 6, 30, 58, 86, 114, 142, 170], // Version 40 ]; /** * Type information coordinates. */ private const TYPE_INFO_COORDINATES = [ [8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [8, 5], [8, 7], [8, 8], [7, 8], [5, 8], [4, 8], [3, 8], [2, 8], [1, 8], [0, 8], ]; /** * Version information polynomial. */ private const VERSION_INFO_POLY = 0x1f25; /** * Type information polynomial. */ private const TYPE_INFO_POLY = 0x537; /** * Type information mask pattern. */ private const TYPE_INFO_MASK_PATTERN = 0x5412; /** * Clears a given matrix. */ public static function clearMatrix(ByteMatrix $matrix) : void { $matrix->clear(-1); } /** * Builds a complete matrix. */ public static function buildMatrix( BitArray $dataBits, ErrorCorrectionLevel $level, Version $version, int $maskPattern, ByteMatrix $matrix ) : void { self::clearMatrix($matrix); self::embedBasicPatterns($version, $matrix); self::embedTypeInfo($level, $maskPattern, $matrix); self::maybeEmbedVersionInfo($version, $matrix); self::embedDataBits($dataBits, $maskPattern, $matrix); } /** * Removes the position detection patterns from a matrix. * * This can be useful if you need to render those patterns separately. */ public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void { $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]); self::removePositionDetectionPattern(0, 0, $matrix); self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix); self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix); } /** * Embeds type information into a matrix. */ private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void { $typeInfoBits = new BitArray(); self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits); $typeInfoBitsSize = $typeInfoBits->getSize(); for ($i = 0; $i < $typeInfoBitsSize; ++$i) { $bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i); $x1 = self::TYPE_INFO_COORDINATES[$i][0]; $y1 = self::TYPE_INFO_COORDINATES[$i][1]; $matrix->set($x1, $y1, (int) $bit); if ($i < 8) { $x2 = $matrix->getWidth() - $i - 1; $y2 = 8; } else { $x2 = 8; $y2 = $matrix->getHeight() - 7 + ($i - 8); } $matrix->set($x2, $y2, (int) $bit); } } /** * Generates type information bits and appends them to a bit array. * * @throws RuntimeException if bit array resulted in invalid size */ private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void { $typeInfo = ($level->getBits() << 3) | $maskPattern; $bits->appendBits($typeInfo, 5); $bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY); $bits->appendBits($bchCode, 10); $maskBits = new BitArray(); $maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15); $bits->xorBits($maskBits); if (15 !== $bits->getSize()) { throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); } } /** * Embeds version information if required. */ private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void { if ($version->getVersionNumber() < 7) { return; } $versionInfoBits = new BitArray(); self::makeVersionInfoBits($version, $versionInfoBits); $bitIndex = 6 * 3 - 1; for ($i = 0; $i < 6; ++$i) { for ($j = 0; $j < 3; ++$j) { $bit = $versionInfoBits->get($bitIndex); --$bitIndex; $matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit); $matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit); } } } /** * Generates version information bits and appends them to a bit array. * * @throws RuntimeException if bit array resulted in invalid size */ private static function makeVersionInfoBits(Version $version, BitArray $bits) : void { $bits->appendBits($version->getVersionNumber(), 6); $bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY); $bits->appendBits($bchCode, 12); if (18 !== $bits->getSize()) { throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize()); } } /** * Calculates the BCH code for a value and a polynomial. */ private static function calculateBchCode(int $value, int $poly) : int { $msbSetInPoly = self::findMsbSet($poly); $value <<= $msbSetInPoly - 1; while (self::findMsbSet($value) >= $msbSetInPoly) { $value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly); } return $value; } /** * Finds and MSB set. */ private static function findMsbSet(int $value) : int { $numDigits = 0; while (0 !== $value) { $value >>= 1; ++$numDigits; } return $numDigits; } /** * Embeds basic patterns into a matrix. */ private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void { self::embedPositionDetectionPatternsAndSeparators($matrix); self::embedDarkDotAtLeftBottomCorner($matrix); self::maybeEmbedPositionAdjustmentPatterns($version, $matrix); self::embedTimingPatterns($matrix); } /** * Embeds position detection patterns and separators into a byte matrix. */ private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void { $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]); self::embedPositionDetectionPattern(0, 0, $matrix); self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix); self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix); $hspWidth = 8; self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix); self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix); self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix); $vspSize = 7; self::embedVerticalSeparationPattern($vspSize, 0, $matrix); self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix); self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix); } /** * Embeds a single position detection pattern into a byte matrix. */ private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void { for ($y = 0; $y < 7; ++$y) { for ($x = 0; $x < 7; ++$x) { $matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]); } } } private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void { for ($y = 0; $y < 7; ++$y) { for ($x = 0; $x < 7; ++$x) { $matrix->set($xStart + $x, $yStart + $y, 0); } } } /** * Embeds a single horizontal separation pattern. * * @throws RuntimeException if a byte was already set */ private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void { for ($x = 0; $x < 8; $x++) { if (-1 !== $matrix->get($xStart + $x, $yStart)) { throw new RuntimeException('Byte already set'); } $matrix->set($xStart + $x, $yStart, 0); } } /** * Embeds a single vertical separation pattern. * * @throws RuntimeException if a byte was already set */ private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void { for ($y = 0; $y < 7; $y++) { if (-1 !== $matrix->get($xStart, $yStart + $y)) { throw new RuntimeException('Byte already set'); } $matrix->set($xStart, $yStart + $y, 0); } } /** * Embeds a dot at the left bottom corner. * * @throws RuntimeException if a byte was already set to 0 */ private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void { if (0 === $matrix->get(8, $matrix->getHeight() - 8)) { throw new RuntimeException('Byte already set to 0'); } $matrix->set(8, $matrix->getHeight() - 8, 1); } /** * Embeds position adjustment patterns if required. */ private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void { if ($version->getVersionNumber() < 2) { return; } $index = $version->getVersionNumber() - 1; $coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index]; $numCoordinates = count($coordinates); for ($i = 0; $i < $numCoordinates; ++$i) { for ($j = 0; $j < $numCoordinates; ++$j) { $y = $coordinates[$i]; $x = $coordinates[$j]; if (null === $x || null === $y) { continue; } if (-1 === $matrix->get($x, $y)) { self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix); } } } } /** * Embeds a single position adjustment pattern. */ private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void { for ($y = 0; $y < 5; $y++) { for ($x = 0; $x < 5; $x++) { $matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]); } } } /** * Embeds timing patterns into a matrix. */ private static function embedTimingPatterns(ByteMatrix $matrix) : void { $matrixWidth = $matrix->getWidth(); for ($i = 8; $i < $matrixWidth - 8; ++$i) { $bit = ($i + 1) % 2; if (-1 === $matrix->get($i, 6)) { $matrix->set($i, 6, $bit); } if (-1 === $matrix->get(6, $i)) { $matrix->set(6, $i, $bit); } } } /** * Embeds "dataBits" using "getMaskPattern". * * For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for * how to embed data bits. * * @throws WriterException if not all bits could be consumed */ private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void { $bitIndex = 0; $direction = -1; // Start from the right bottom cell. $x = $matrix->getWidth() - 1; $y = $matrix->getHeight() - 1; while ($x > 0) { // Skip vertical timing pattern. if (6 === $x) { --$x; } while ($y >= 0 && $y < $matrix->getHeight()) { for ($i = 0; $i < 2; $i++) { $xx = $x - $i; // Skip the cell if it's not empty. if (-1 !== $matrix->get($xx, $y)) { continue; } if ($bitIndex < $dataBits->getSize()) { $bit = $dataBits->get($bitIndex); ++$bitIndex; } else { // Padding bit. If there is no bit left, we'll fill the // left cells with 0, as described in 8.4.9 of // JISX0510:2004 (p. 24). $bit = false; } // Skip masking if maskPattern is -1. if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) { $bit = ! $bit; } $matrix->set($xx, $y, (int) $bit); } $y += $direction; } $direction = -$direction; $y += $direction; $x -= 2; } // All bits should be consumed if ($dataBits->getSize() !== $bitIndex) { throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')'); } } } src/Encoder/.htaccess000077700000000177151323635260010532 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/Eye/ModuleEye.php000077700000002253151323635260012243 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Eye; use BaconQrCode\Encoder\ByteMatrix; use BaconQrCode\Renderer\Module\ModuleInterface; use BaconQrCode\Renderer\Path\Path; /** * Renders an eye based on a module renderer. */ final class ModuleEye implements EyeInterface { /** * @var ModuleInterface */ private $module; public function __construct(ModuleInterface $module) { $this->module = $module; } public function getExternalPath() : Path { $matrix = new ByteMatrix(7, 7); for ($x = 0; $x < 7; ++$x) { $matrix->set($x, 0, 1); $matrix->set($x, 6, 1); } for ($y = 1; $y < 6; ++$y) { $matrix->set(0, $y, 1); $matrix->set(6, $y, 1); } return $this->module->createPath($matrix)->translate(-3.5, -3.5); } public function getInternalPath() : Path { $matrix = new ByteMatrix(3, 3); for ($x = 0; $x < 3; ++$x) { for ($y = 0; $y < 3; ++$y) { $matrix->set($x, $y, 1); } } return $this->module->createPath($matrix)->translate(-1.5, -1.5); } } src/Renderer/Eye/SquareEye.php000077700000002061151323635260012253 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Eye; use BaconQrCode\Renderer\Path\Path; /** * Renders the eyes in their default square shape. */ final class SquareEye implements EyeInterface { /** * @var self|null */ private static $instance; private function __construct() { } public static function instance() : self { return self::$instance ?: self::$instance = new self(); } public function getExternalPath() : Path { return (new Path()) ->move(-3.5, -3.5) ->line(3.5, -3.5) ->line(3.5, 3.5) ->line(-3.5, 3.5) ->close() ->move(-2.5, -2.5) ->line(-2.5, 2.5) ->line(2.5, 2.5) ->line(2.5, -2.5) ->close() ; } public function getInternalPath() : Path { return (new Path()) ->move(-1.5, -1.5) ->line(1.5, -1.5) ->line(1.5, 1.5) ->line(-1.5, 1.5) ->close() ; } } src/Renderer/Eye/SimpleCircleEye.php000077700000002307151323635260013371 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Eye; use BaconQrCode\Renderer\Path\Path; /** * Renders the inner eye as a circle. */ final class SimpleCircleEye implements EyeInterface { /** * @var self|null */ private static $instance; private function __construct() { } public static function instance() : self { return self::$instance ?: self::$instance = new self(); } public function getExternalPath() : Path { return (new Path()) ->move(-3.5, -3.5) ->line(3.5, -3.5) ->line(3.5, 3.5) ->line(-3.5, 3.5) ->close() ->move(-2.5, -2.5) ->line(-2.5, 2.5) ->line(2.5, 2.5) ->line(2.5, -2.5) ->close() ; } public function getInternalPath() : Path { return (new Path()) ->move(1.5, 0) ->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5) ->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.) ->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5) ->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.) ->close() ; } } src/Renderer/Eye/EyeInterface.php000077700000001124151323635260012712 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Eye; use BaconQrCode\Renderer\Path\Path; /** * Interface for describing the look of an eye. */ interface EyeInterface { /** * Returns the path of the external eye element. * * The path origin point (0, 0) must be anchored at the middle of the path. */ public function getExternalPath() : Path; /** * Returns the path of the internal eye element. * * The path origin point (0, 0) must be anchored at the middle of the path. */ public function getInternalPath() : Path; } src/Renderer/Eye/.htaccess000077700000000177151323635260011443 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/Eye/CompositeEye.php000077700000001366151323635260012764 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Eye; use BaconQrCode\Renderer\Path\Path; /** * Combines the style of two different eyes. */ final class CompositeEye implements EyeInterface { /** * @var EyeInterface */ private $externalEye; /** * @var EyeInterface */ private $internalEye; public function __construct(EyeInterface $externalEye, EyeInterface $internalEye) { $this->externalEye = $externalEye; $this->internalEye = $internalEye; } public function getExternalPath() : Path { return $this->externalEye->getExternalPath(); } public function getInternalPath() : Path { return $this->externalEye->getInternalPath(); } } src/Renderer/PlainTextRenderer.php000077700000004216151323635260013231 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer; use BaconQrCode\Encoder\QrCode; use BaconQrCode\Exception\InvalidArgumentException; final class PlainTextRenderer implements RendererInterface { /** * UTF-8 full block (U+2588) */ private const FULL_BLOCK = "\xe2\x96\x88"; /** * UTF-8 upper half block (U+2580) */ private const UPPER_HALF_BLOCK = "\xe2\x96\x80"; /** * UTF-8 lower half block (U+2584) */ private const LOWER_HALF_BLOCK = "\xe2\x96\x84"; /** * UTF-8 no-break space (U+00A0) */ private const EMPTY_BLOCK = "\xc2\xa0"; /** * @var int */ private $margin; public function __construct(int $margin = 2) { $this->margin = $margin; } /** * @throws InvalidArgumentException if matrix width doesn't match height */ public function render(QrCode $qrCode) : string { $matrix = $qrCode->getMatrix(); $matrixSize = $matrix->getWidth(); if ($matrixSize !== $matrix->getHeight()) { throw new InvalidArgumentException('Matrix must have the same width and height'); } $rows = $matrix->getArray()->toArray(); if (0 !== $matrixSize % 2) { $rows[] = array_fill(0, $matrixSize, 0); } $horizontalMargin = str_repeat(self::EMPTY_BLOCK, $this->margin); $result = str_repeat("\n", (int) ceil($this->margin / 2)); for ($i = 0; $i < $matrixSize; $i += 2) { $result .= $horizontalMargin; $upperRow = $rows[$i]; $lowerRow = $rows[$i + 1]; for ($j = 0; $j < $matrixSize; ++$j) { $upperBit = $upperRow[$j]; $lowerBit = $lowerRow[$j]; if ($upperBit) { $result .= $lowerBit ? self::FULL_BLOCK : self::UPPER_HALF_BLOCK; } else { $result .= $lowerBit ? self::LOWER_HALF_BLOCK : self::EMPTY_BLOCK; } } $result .= $horizontalMargin . "\n"; } $result .= str_repeat("\n", (int) ceil($this->margin / 2)); return $result; } } src/Renderer/Color/Alpha.php000077700000002076151323635260011737 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Color; use BaconQrCode\Exception; final class Alpha implements ColorInterface { /** * @var int */ private $alpha; /** * @var ColorInterface */ private $baseColor; /** * @param int $alpha the alpha value, 0 to 100 */ public function __construct(int $alpha, ColorInterface $baseColor) { if ($alpha < 0 || $alpha > 100) { throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100'); } $this->alpha = $alpha; $this->baseColor = $baseColor; } public function getAlpha() : int { return $this->alpha; } public function getBaseColor() : ColorInterface { return $this->baseColor; } public function toRgb() : Rgb { return $this->baseColor->toRgb(); } public function toCmyk() : Cmyk { return $this->baseColor->toCmyk(); } public function toGray() : Gray { return $this->baseColor->toGray(); } } src/Renderer/Color/Rgb.php000077700000003645151323635260011427 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Color; use BaconQrCode\Exception; final class Rgb implements ColorInterface { /** * @var int */ private $red; /** * @var int */ private $green; /** * @var int */ private $blue; /** * @param int $red the red amount of the color, 0 to 255 * @param int $green the green amount of the color, 0 to 255 * @param int $blue the blue amount of the color, 0 to 255 */ public function __construct(int $red, int $green, int $blue) { if ($red < 0 || $red > 255) { throw new Exception\InvalidArgumentException('Red must be between 0 and 255'); } if ($green < 0 || $green > 255) { throw new Exception\InvalidArgumentException('Green must be between 0 and 255'); } if ($blue < 0 || $blue > 255) { throw new Exception\InvalidArgumentException('Blue must be between 0 and 255'); } $this->red = $red; $this->green = $green; $this->blue = $blue; } public function getRed() : int { return $this->red; } public function getGreen() : int { return $this->green; } public function getBlue() : int { return $this->blue; } public function toRgb() : Rgb { return $this; } public function toCmyk() : Cmyk { $c = 1 - ($this->red / 255); $m = 1 - ($this->green / 255); $y = 1 - ($this->blue / 255); $k = min($c, $m, $y); return new Cmyk( (int) (100 * ($c - $k) / (1 - $k)), (int) (100 * ($m - $k) / (1 - $k)), (int) (100 * ($y - $k) / (1 - $k)), (int) (100 * $k) ); } public function toGray() : Gray { return new Gray((int) (($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55)); } } src/Renderer/Color/Cmyk.php000077700000004412151323635260011611 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Color; use BaconQrCode\Exception; final class Cmyk implements ColorInterface { /** * @var int */ private $cyan; /** * @var int */ private $magenta; /** * @var int */ private $yellow; /** * @var int */ private $black; /** * @param int $cyan the cyan amount, 0 to 100 * @param int $magenta the magenta amount, 0 to 100 * @param int $yellow the yellow amount, 0 to 100 * @param int $black the black amount, 0 to 100 */ public function __construct(int $cyan, int $magenta, int $yellow, int $black) { if ($cyan < 0 || $cyan > 100) { throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100'); } if ($magenta < 0 || $magenta > 100) { throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100'); } if ($yellow < 0 || $yellow > 100) { throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100'); } if ($black < 0 || $black > 100) { throw new Exception\InvalidArgumentException('Black must be between 0 and 100'); } $this->cyan = $cyan; $this->magenta = $magenta; $this->yellow = $yellow; $this->black = $black; } public function getCyan() : int { return $this->cyan; } public function getMagenta() : int { return $this->magenta; } public function getYellow() : int { return $this->yellow; } public function getBlack() : int { return $this->black; } public function toRgb() : Rgb { $k = $this->black / 100; $c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100; $m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100; $y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100; return new Rgb( (int) (-$c * 255 + 255), (int) (-$m * 255 + 255), (int) (-$y * 255 + 255) ); } public function toCmyk() : Cmyk { return $this; } public function toGray() : Gray { return $this->toRgb()->toGray(); } } src/Renderer/Color/Gray.php000077700000001637151323635260011616 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Color; use BaconQrCode\Exception; final class Gray implements ColorInterface { /** * @var int */ private $gray; /** * @param int $gray the gray value between 0 (black) and 100 (white) */ public function __construct(int $gray) { if ($gray < 0 || $gray > 100) { throw new Exception\InvalidArgumentException('Gray must be between 0 and 100'); } $this->gray = (int) $gray; } public function getGray() : int { return $this->gray; } public function toRgb() : Rgb { return new Rgb((int) ($this->gray * 2.55), (int) ($this->gray * 2.55), (int) ($this->gray * 2.55)); } public function toCmyk() : Cmyk { return new Cmyk(0, 0, 0, 100 - $this->gray); } public function toGray() : Gray { return $this; } } src/Renderer/Color/ColorInterface.php000077700000000555151323635260013611 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Color; interface ColorInterface { /** * Converts the color to RGB. */ public function toRgb() : Rgb; /** * Converts the color to CMYK. */ public function toCmyk() : Cmyk; /** * Converts the color to gray. */ public function toGray() : Gray; } src/Renderer/Color/.htaccess000077700000000177151323635260011777 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/RendererInterface.php000077700000000271151323635260013216 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer; use BaconQrCode\Encoder\QrCode; interface RendererInterface { public function render(QrCode $qrCode) : string; } src/Renderer/RendererStyle/Gradient.php000077700000001532151323635260014154 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\RendererStyle; use BaconQrCode\Renderer\Color\ColorInterface; final class Gradient { /** * @var ColorInterface */ private $startColor; /** * @var ColorInterface */ private $endColor; /** * @var GradientType */ private $type; public function __construct(ColorInterface $startColor, ColorInterface $endColor, GradientType $type) { $this->startColor = $startColor; $this->endColor = $endColor; $this->type = $type; } public function getStartColor() : ColorInterface { return $this->startColor; } public function getEndColor() : ColorInterface { return $this->endColor; } public function getType() : GradientType { return $this->type; } } src/Renderer/RendererStyle/EyeFill.php000077700000003301151323635260013744 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\RendererStyle; use BaconQrCode\Exception\RuntimeException; use BaconQrCode\Renderer\Color\ColorInterface; final class EyeFill { /** * @var ColorInterface|null */ private $externalColor; /** * @var ColorInterface|null */ private $internalColor; /** * @var self|null */ private static $inherit; public function __construct(?ColorInterface $externalColor, ?ColorInterface $internalColor) { $this->externalColor = $externalColor; $this->internalColor = $internalColor; } public static function uniform(ColorInterface $color) : self { return new self($color, $color); } public static function inherit() : self { return self::$inherit ?: self::$inherit = new self(null, null); } public function inheritsBothColors() : bool { return null === $this->externalColor && null === $this->internalColor; } public function inheritsExternalColor() : bool { return null === $this->externalColor; } public function inheritsInternalColor() : bool { return null === $this->internalColor; } public function getExternalColor() : ColorInterface { if (null === $this->externalColor) { throw new RuntimeException('External eye color inherits foreground color'); } return $this->externalColor; } public function getInternalColor() : ColorInterface { if (null === $this->internalColor) { throw new RuntimeException('Internal eye color inherits foreground color'); } return $this->internalColor; } } src/Renderer/RendererStyle/GradientType.php000077700000001036151323635260015015 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\RendererStyle; use DASPRiD\Enum\AbstractEnum; /** * @method static self VERTICAL() * @method static self HORIZONTAL() * @method static self DIAGONAL() * @method static self INVERSE_DIAGONAL() * @method static self RADIAL() */ final class GradientType extends AbstractEnum { protected const VERTICAL = null; protected const HORIZONTAL = null; protected const DIAGONAL = null; protected const INVERSE_DIAGONAL = null; protected const RADIAL = null; } src/Renderer/RendererStyle/RendererStyle.php000077700000003257151323635260015214 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\RendererStyle; use BaconQrCode\Renderer\Eye\EyeInterface; use BaconQrCode\Renderer\Eye\ModuleEye; use BaconQrCode\Renderer\Module\ModuleInterface; use BaconQrCode\Renderer\Module\SquareModule; final class RendererStyle { /** * @var int */ private $size; /** * @var int */ private $margin; /** * @var ModuleInterface */ private $module; /** * @var EyeInterface|null */ private $eye; /** * @var Fill */ private $fill; public function __construct( int $size, int $margin = 4, ?ModuleInterface $module = null, ?EyeInterface $eye = null, ?Fill $fill = null ) { $this->margin = $margin; $this->size = $size; $this->module = $module ?: SquareModule::instance(); $this->eye = $eye ?: new ModuleEye($this->module); $this->fill = $fill ?: Fill::default(); } public function withSize(int $size) : self { $style = clone $this; $style->size = $size; return $style; } public function withMargin(int $margin) : self { $style = clone $this; $style->margin = $margin; return $style; } public function getSize() : int { return $this->size; } public function getMargin() : int { return $this->margin; } public function getModule() : ModuleInterface { return $this->module; } public function getEye() : EyeInterface { return $this->eye; } public function getFill() : Fill { return $this->fill; } } src/Renderer/RendererStyle/Fill.php000077700000010061151323635260013302 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\RendererStyle; use BaconQrCode\Exception\RuntimeException; use BaconQrCode\Renderer\Color\ColorInterface; use BaconQrCode\Renderer\Color\Gray; final class Fill { /** * @var ColorInterface */ private $backgroundColor; /** * @var ColorInterface|null */ private $foregroundColor; /** * @var Gradient|null */ private $foregroundGradient; /** * @var EyeFill */ private $topLeftEyeFill; /** * @var EyeFill */ private $topRightEyeFill; /** * @var EyeFill */ private $bottomLeftEyeFill; /** * @var self|null */ private static $default; private function __construct( ColorInterface $backgroundColor, ?ColorInterface $foregroundColor, ?Gradient $foregroundGradient, EyeFill $topLeftEyeFill, EyeFill $topRightEyeFill, EyeFill $bottomLeftEyeFill ) { $this->backgroundColor = $backgroundColor; $this->foregroundColor = $foregroundColor; $this->foregroundGradient = $foregroundGradient; $this->topLeftEyeFill = $topLeftEyeFill; $this->topRightEyeFill = $topRightEyeFill; $this->bottomLeftEyeFill = $bottomLeftEyeFill; } public static function default() : self { return self::$default ?: self::$default = self::uniformColor(new Gray(100), new Gray(0)); } public static function withForegroundColor( ColorInterface $backgroundColor, ColorInterface $foregroundColor, EyeFill $topLeftEyeFill, EyeFill $topRightEyeFill, EyeFill $bottomLeftEyeFill ) : self { return new self( $backgroundColor, $foregroundColor, null, $topLeftEyeFill, $topRightEyeFill, $bottomLeftEyeFill ); } public static function withForegroundGradient( ColorInterface $backgroundColor, Gradient $foregroundGradient, EyeFill $topLeftEyeFill, EyeFill $topRightEyeFill, EyeFill $bottomLeftEyeFill ) : self { return new self( $backgroundColor, null, $foregroundGradient, $topLeftEyeFill, $topRightEyeFill, $bottomLeftEyeFill ); } public static function uniformColor(ColorInterface $backgroundColor, ColorInterface $foregroundColor) : self { return new self( $backgroundColor, $foregroundColor, null, EyeFill::inherit(), EyeFill::inherit(), EyeFill::inherit() ); } public static function uniformGradient(ColorInterface $backgroundColor, Gradient $foregroundGradient) : self { return new self( $backgroundColor, null, $foregroundGradient, EyeFill::inherit(), EyeFill::inherit(), EyeFill::inherit() ); } public function hasGradientFill() : bool { return null !== $this->foregroundGradient; } public function getBackgroundColor() : ColorInterface { return $this->backgroundColor; } public function getForegroundColor() : ColorInterface { if (null === $this->foregroundColor) { throw new RuntimeException('Fill uses a gradient, thus no foreground color is available'); } return $this->foregroundColor; } public function getForegroundGradient() : Gradient { if (null === $this->foregroundGradient) { throw new RuntimeException('Fill uses a single color, thus no foreground gradient is available'); } return $this->foregroundGradient; } public function getTopLeftEyeFill() : EyeFill { return $this->topLeftEyeFill; } public function getTopRightEyeFill() : EyeFill { return $this->topRightEyeFill; } public function getBottomLeftEyeFill() : EyeFill { return $this->bottomLeftEyeFill; } } src/Renderer/RendererStyle/.htaccess000077700000000177151323635260013510 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/Path/EllipticArc.php000077700000015515151323635260012725 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Path; final class EllipticArc implements OperationInterface { private const ZERO_TOLERANCE = 1e-05; /** * @var float */ private $xRadius; /** * @var float */ private $yRadius; /** * @var float */ private $xAxisAngle; /** * @var bool */ private $largeArc; /** * @var bool */ private $sweep; /** * @var float */ private $x; /** * @var float */ private $y; public function __construct( float $xRadius, float $yRadius, float $xAxisAngle, bool $largeArc, bool $sweep, float $x, float $y ) { $this->xRadius = abs($xRadius); $this->yRadius = abs($yRadius); $this->xAxisAngle = $xAxisAngle % 360; $this->largeArc = $largeArc; $this->sweep = $sweep; $this->x = $x; $this->y = $y; } public function getXRadius() : float { return $this->xRadius; } public function getYRadius() : float { return $this->yRadius; } public function getXAxisAngle() : float { return $this->xAxisAngle; } public function isLargeArc() : bool { return $this->largeArc; } public function isSweep() : bool { return $this->sweep; } public function getX() : float { return $this->x; } public function getY() : float { return $this->y; } /** * @return self */ public function translate(float $x, float $y) : OperationInterface { return new self( $this->xRadius, $this->yRadius, $this->xAxisAngle, $this->largeArc, $this->sweep, $this->x + $x, $this->y + $y ); } /** * Converts the elliptic arc to multiple curves. * * Since not all image back ends support elliptic arcs, this method allows to convert the arc into multiple curves * resembling the same result. * * @see https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ * @return array<Curve|Line> */ public function toCurves(float $fromX, float $fromY) : array { if (sqrt(($fromX - $this->x) ** 2 + ($fromY - $this->y) ** 2) < self::ZERO_TOLERANCE) { return []; } if ($this->xRadius < self::ZERO_TOLERANCE || $this->yRadius < self::ZERO_TOLERANCE) { return [new Line($this->x, $this->y)]; } return $this->createCurves($fromX, $fromY); } /** * @return Curve[] */ private function createCurves(float $fromX, $fromY) : array { $xAngle = deg2rad($this->xAxisAngle); list($centerX, $centerY, $radiusX, $radiusY, $startAngle, $deltaAngle) = $this->calculateCenterPointParameters($fromX, $fromY, $xAngle); $s = $startAngle; $e = $s + $deltaAngle; $sign = ($e < $s) ? -1 : 1; $remain = abs($e - $s); $p1 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s); $curves = []; while ($remain > self::ZERO_TOLERANCE) { $step = min($remain, pi() / 2); $signStep = $step * $sign; $p2 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s + $signStep); $alphaT = tan($signStep / 2); $alpha = sin($signStep) * (sqrt(4 + 3 * $alphaT ** 2) - 1) / 3; $d1 = self::derivative($radiusX, $radiusY, $xAngle, $s); $d2 = self::derivative($radiusX, $radiusY, $xAngle, $s + $signStep); $curves[] = new Curve( $p1[0] + $alpha * $d1[0], $p1[1] + $alpha * $d1[1], $p2[0] - $alpha * $d2[0], $p2[1] - $alpha * $d2[1], $p2[0], $p2[1] ); $s += $signStep; $remain -= $step; $p1 = $p2; } return $curves; } /** * @return float[] */ private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle) { $rX = $this->xRadius; $rY = $this->yRadius; // F.6.5.1 $dx2 = ($fromX - $this->x) / 2; $dy2 = ($fromY - $this->y) / 2; $x1p = cos($xAngle) * $dx2 + sin($xAngle) * $dy2; $y1p = -sin($xAngle) * $dx2 + cos($xAngle) * $dy2; // F.6.5.2 $rxs = $rX ** 2; $rys = $rY ** 2; $x1ps = $x1p ** 2; $y1ps = $y1p ** 2; $cr = $x1ps / $rxs + $y1ps / $rys; if ($cr > 1) { $s = sqrt($cr); $rX *= $s; $rY *= $s; $rxs = $rX ** 2; $rys = $rY ** 2; } $dq = ($rxs * $y1ps + $rys * $x1ps); $pq = ($rxs * $rys - $dq) / $dq; $q = sqrt(max(0, $pq)); if ($this->largeArc === $this->sweep) { $q = -$q; } $cxp = $q * $rX * $y1p / $rY; $cyp = -$q * $rY * $x1p / $rX; // F.6.5.3 $cx = cos($xAngle) * $cxp - sin($xAngle) * $cyp + ($fromX + $this->x) / 2; $cy = sin($xAngle) * $cxp + cos($xAngle) * $cyp + ($fromY + $this->y) / 2; // F.6.5.5 $theta = self::angle(1, 0, ($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY); // F.6.5.6 $delta = self::angle(($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY, (-$x1p - $cxp) / $rX, (-$y1p - $cyp) / $rY); $delta = fmod($delta, pi() * 2); if (! $this->sweep) { $delta -= 2 * pi(); } return [$cx, $cy, $rX, $rY, $theta, $delta]; } private static function angle(float $ux, float $uy, float $vx, float $vy) : float { // F.6.5.4 $dot = $ux * $vx + $uy * $vy; $length = sqrt($ux ** 2 + $uy ** 2) * sqrt($vx ** 2 + $vy ** 2); $angle = acos(min(1, max(-1, $dot / $length))); if (($ux * $vy - $uy * $vx) < 0) { return -$angle; } return $angle; } /** * @return float[] */ private static function point( float $centerX, float $centerY, float $radiusX, float $radiusY, float $xAngle, float $angle ) : array { return [ $centerX + $radiusX * cos($xAngle) * cos($angle) - $radiusY * sin($xAngle) * sin($angle), $centerY + $radiusX * sin($xAngle) * cos($angle) + $radiusY * cos($xAngle) * sin($angle), ]; } /** * @return float[] */ private static function derivative(float $radiusX, float $radiusY, float $xAngle, float $angle) : array { return [ -$radiusX * cos($xAngle) * sin($angle) - $radiusY * sin($xAngle) * cos($angle), -$radiusX * sin($xAngle) * sin($angle) + $radiusY * cos($xAngle) * cos($angle), ]; } } src/Renderer/Path/Curve.php000077700000002675151323635260011621 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Path; final class Curve implements OperationInterface { /** * @var float */ private $x1; /** * @var float */ private $y1; /** * @var float */ private $x2; /** * @var float */ private $y2; /** * @var float */ private $x3; /** * @var float */ private $y3; public function __construct(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3) { $this->x1 = $x1; $this->y1 = $y1; $this->x2 = $x2; $this->y2 = $y2; $this->x3 = $x3; $this->y3 = $y3; } public function getX1() : float { return $this->x1; } public function getY1() : float { return $this->y1; } public function getX2() : float { return $this->x2; } public function getY2() : float { return $this->y2; } public function getX3() : float { return $this->x3; } public function getY3() : float { return $this->y3; } /** * @return self */ public function translate(float $x, float $y) : OperationInterface { return new self( $this->x1 + $x, $this->y1 + $y, $this->x2 + $x, $this->y2 + $y, $this->x3 + $x, $this->y3 + $y ); } } src/Renderer/Path/OperationInterface.php000077700000000342151323635260014303 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Path; interface OperationInterface { /** * Translates the operation's coordinates. */ public function translate(float $x, float $y) : self; } src/Renderer/Path/Line.php000077700000001220151323635260011405 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Path; final class Line implements OperationInterface { /** * @var float */ private $x; /** * @var float */ private $y; public function __construct(float $x, float $y) { $this->x = $x; $this->y = $y; } public function getX() : float { return $this->x; } public function getY() : float { return $this->y; } /** * @return self */ public function translate(float $x, float $y) : OperationInterface { return new self($this->x + $x, $this->y + $y); } } src/Renderer/Path/Path.php000077700000004644151323635260011427 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Path; use IteratorAggregate; use Traversable; /** * Internal Representation of a vector path. */ final class Path implements IteratorAggregate { /** * @var OperationInterface[] */ private $operations = []; /** * Moves the drawing operation to a certain position. */ public function move(float $x, float $y) : self { $path = clone $this; $path->operations[] = new Move($x, $y); return $path; } /** * Draws a line from the current position to another position. */ public function line(float $x, float $y) : self { $path = clone $this; $path->operations[] = new Line($x, $y); return $path; } /** * Draws an elliptic arc from the current position to another position. */ public function ellipticArc( float $xRadius, float $yRadius, float $xAxisRotation, bool $largeArc, bool $sweep, float $x, float $y ) : self { $path = clone $this; $path->operations[] = new EllipticArc($xRadius, $yRadius, $xAxisRotation, $largeArc, $sweep, $x, $y); return $path; } /** * Draws a curve from the current position to another position. */ public function curve(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3) : self { $path = clone $this; $path->operations[] = new Curve($x1, $y1, $x2, $y2, $x3, $y3); return $path; } /** * Closes a sub-path. */ public function close() : self { $path = clone $this; $path->operations[] = Close::instance(); return $path; } /** * Appends another path to this one. */ public function append(self $other) : self { $path = clone $this; $path->operations = array_merge($this->operations, $other->operations); return $path; } public function translate(float $x, float $y) : self { $path = new self(); foreach ($this->operations as $operation) { $path->operations[] = $operation->translate($x, $y); } return $path; } /** * @return OperationInterface[]|Traversable */ public function getIterator() : Traversable { foreach ($this->operations as $operation) { yield $operation; } } } src/Renderer/Path/Close.php000077700000000770151323635260011574 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Path; final class Close implements OperationInterface { /** * @var self|null */ private static $instance; private function __construct() { } public static function instance() : self { return self::$instance ?: self::$instance = new self(); } /** * @return self */ public function translate(float $x, float $y) : OperationInterface { return $this; } } src/Renderer/Path/Move.php000077700000001220151323635260011424 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Path; final class Move implements OperationInterface { /** * @var float */ private $x; /** * @var float */ private $y; public function __construct(float $x, float $y) { $this->x = $x; $this->y = $y; } public function getX() : float { return $this->x; } public function getY() : float { return $this->y; } /** * @return self */ public function translate(float $x, float $y) : OperationInterface { return new self($this->x + $x, $this->y + $y); } } src/Renderer/Path/.htaccess000077700000000177151323635260011615 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/ImageRenderer.php000077700000011014151323635260012335 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer; use BaconQrCode\Encoder\MatrixUtil; use BaconQrCode\Encoder\QrCode; use BaconQrCode\Exception\InvalidArgumentException; use BaconQrCode\Renderer\Image\ImageBackEndInterface; use BaconQrCode\Renderer\Path\Path; use BaconQrCode\Renderer\RendererStyle\EyeFill; use BaconQrCode\Renderer\RendererStyle\RendererStyle; final class ImageRenderer implements RendererInterface { /** * @var RendererStyle */ private $rendererStyle; /** * @var ImageBackEndInterface */ private $imageBackEnd; public function __construct(RendererStyle $rendererStyle, ImageBackEndInterface $imageBackEnd) { $this->rendererStyle = $rendererStyle; $this->imageBackEnd = $imageBackEnd; } /** * @throws InvalidArgumentException if matrix width doesn't match height */ public function render(QrCode $qrCode) : string { $size = $this->rendererStyle->getSize(); $margin = $this->rendererStyle->getMargin(); $matrix = $qrCode->getMatrix(); $matrixSize = $matrix->getWidth(); if ($matrixSize !== $matrix->getHeight()) { throw new InvalidArgumentException('Matrix must have the same width and height'); } $totalSize = $matrixSize + ($margin * 2); $moduleSize = $size / $totalSize; $fill = $this->rendererStyle->getFill(); $this->imageBackEnd->new($size, $fill->getBackgroundColor()); $this->imageBackEnd->scale((float) $moduleSize); $this->imageBackEnd->translate((float) $margin, (float) $margin); $module = $this->rendererStyle->getModule(); $moduleMatrix = clone $matrix; MatrixUtil::removePositionDetectionPatterns($moduleMatrix); $modulePath = $this->drawEyes($matrixSize, $module->createPath($moduleMatrix)); if ($fill->hasGradientFill()) { $this->imageBackEnd->drawPathWithGradient( $modulePath, $fill->getForegroundGradient(), 0, 0, $matrixSize, $matrixSize ); } else { $this->imageBackEnd->drawPathWithColor($modulePath, $fill->getForegroundColor()); } return $this->imageBackEnd->done(); } private function drawEyes(int $matrixSize, Path $modulePath) : Path { $fill = $this->rendererStyle->getFill(); $eye = $this->rendererStyle->getEye(); $externalPath = $eye->getExternalPath(); $internalPath = $eye->getInternalPath(); $modulePath = $this->drawEye( $externalPath, $internalPath, $fill->getTopLeftEyeFill(), 3.5, 3.5, 0, $modulePath ); $modulePath = $this->drawEye( $externalPath, $internalPath, $fill->getTopRightEyeFill(), $matrixSize - 3.5, 3.5, 90, $modulePath ); $modulePath = $this->drawEye( $externalPath, $internalPath, $fill->getBottomLeftEyeFill(), 3.5, $matrixSize - 3.5, -90, $modulePath ); return $modulePath; } private function drawEye( Path $externalPath, Path $internalPath, EyeFill $fill, float $xTranslation, float $yTranslation, int $rotation, Path $modulePath ) : Path { if ($fill->inheritsBothColors()) { return $modulePath ->append($externalPath->translate($xTranslation, $yTranslation)) ->append($internalPath->translate($xTranslation, $yTranslation)); } $this->imageBackEnd->push(); $this->imageBackEnd->translate($xTranslation, $yTranslation); if (0 !== $rotation) { $this->imageBackEnd->rotate($rotation); } if ($fill->inheritsExternalColor()) { $modulePath = $modulePath->append($externalPath->translate($xTranslation, $yTranslation)); } else { $this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor()); } if ($fill->inheritsInternalColor()) { $modulePath = $modulePath->append($internalPath->translate($xTranslation, $yTranslation)); } else { $this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor()); } $this->imageBackEnd->pop(); return $modulePath; } } src/Renderer/Image/TransformationMatrix.php000077700000003771151323635260015054 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Image; final class TransformationMatrix { /** * @var float[] */ private $values; public function __construct() { $this->values = [1, 0, 0, 1, 0, 0]; } public function multiply(self $other) : self { $matrix = new self(); $matrix->values[0] = $this->values[0] * $other->values[0] + $this->values[2] * $other->values[1]; $matrix->values[1] = $this->values[1] * $other->values[0] + $this->values[3] * $other->values[1]; $matrix->values[2] = $this->values[0] * $other->values[2] + $this->values[2] * $other->values[3]; $matrix->values[3] = $this->values[1] * $other->values[2] + $this->values[3] * $other->values[3]; $matrix->values[4] = $this->values[0] * $other->values[4] + $this->values[2] * $other->values[5] + $this->values[4]; $matrix->values[5] = $this->values[1] * $other->values[4] + $this->values[3] * $other->values[5] + $this->values[5]; return $matrix; } public static function scale(float $size) : self { $matrix = new self(); $matrix->values = [$size, 0, 0, $size, 0, 0]; return $matrix; } public static function translate(float $x, float $y) : self { $matrix = new self(); $matrix->values = [1, 0, 0, 1, $x, $y]; return $matrix; } public static function rotate(int $degrees) : self { $matrix = new self(); $rad = deg2rad($degrees); $matrix->values = [cos($rad), sin($rad), -sin($rad), cos($rad), 0, 0]; return $matrix; } /** * Applies this matrix onto a point and returns the resulting viewport point. * * @return float[] */ public function apply(float $x, float $y) : array { return [ $x * $this->values[0] + $y * $this->values[2] + $this->values[4], $x * $this->values[1] + $y * $this->values[3] + $this->values[5], ]; } } src/Renderer/Image/EpsImageBackEnd.php000077700000027273151323635260013566 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Image; use BaconQrCode\Exception\RuntimeException; use BaconQrCode\Renderer\Color\Alpha; use BaconQrCode\Renderer\Color\Cmyk; use BaconQrCode\Renderer\Color\ColorInterface; use BaconQrCode\Renderer\Color\Gray; use BaconQrCode\Renderer\Color\Rgb; use BaconQrCode\Renderer\Path\Close; use BaconQrCode\Renderer\Path\Curve; use BaconQrCode\Renderer\Path\EllipticArc; use BaconQrCode\Renderer\Path\Line; use BaconQrCode\Renderer\Path\Move; use BaconQrCode\Renderer\Path\Path; use BaconQrCode\Renderer\RendererStyle\Gradient; use BaconQrCode\Renderer\RendererStyle\GradientType; final class EpsImageBackEnd implements ImageBackEndInterface { private const PRECISION = 3; /** * @var string|null */ private $eps; public function new(int $size, ColorInterface $backgroundColor) : void { $this->eps = "%!PS-Adobe-3.0 EPSF-3.0\n" . "%%Creator: BaconQrCode\n" . sprintf("%%%%BoundingBox: 0 0 %d %d \n", $size, $size) . "%%BeginProlog\n" . "save\n" . "50 dict begin\n" . "/q { gsave } bind def\n" . "/Q { grestore } bind def\n" . "/s { scale } bind def\n" . "/t { translate } bind def\n" . "/r { rotate } bind def\n" . "/n { newpath } bind def\n" . "/m { moveto } bind def\n" . "/l { lineto } bind def\n" . "/c { curveto } bind def\n" . "/z { closepath } bind def\n" . "/f { eofill } bind def\n" . "/rgb { setrgbcolor } bind def\n" . "/cmyk { setcmykcolor } bind def\n" . "/gray { setgray } bind def\n" . "%%EndProlog\n" . "1 -1 s\n" . sprintf("0 -%d t\n", $size); if ($backgroundColor instanceof Alpha && 0 === $backgroundColor->getAlpha()) { return; } $this->eps .= wordwrap( '0 0 m' . sprintf(' %s 0 l', (string) $size) . sprintf(' %s %s l', (string) $size, (string) $size) . sprintf(' 0 %s l', (string) $size) . ' z' . ' ' .$this->getColorSetString($backgroundColor) . " f\n", 75, "\n " ); } public function scale(float $size) : void { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $this->eps .= sprintf("%1\$s %1\$s s\n", round($size, self::PRECISION)); } public function translate(float $x, float $y) : void { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $this->eps .= sprintf("%s %s t\n", round($x, self::PRECISION), round($y, self::PRECISION)); } public function rotate(int $degrees) : void { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $this->eps .= sprintf("%d r\n", $degrees); } public function push() : void { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $this->eps .= "q\n"; } public function pop() : void { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $this->eps .= "Q\n"; } public function drawPathWithColor(Path $path, ColorInterface $color) : void { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $fromX = 0; $fromY = 0; $this->eps .= wordwrap( 'n ' . $this->drawPathOperations($path, $fromX, $fromY) . ' ' . $this->getColorSetString($color) . " f\n", 75, "\n " ); } public function drawPathWithGradient( Path $path, Gradient $gradient, float $x, float $y, float $width, float $height ) : void { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $fromX = 0; $fromY = 0; $this->eps .= wordwrap( 'q n ' . $this->drawPathOperations($path, $fromX, $fromY) . "\n", 75, "\n " ); $this->createGradientFill($gradient, $x, $y, $width, $height); } public function done() : string { if (null === $this->eps) { throw new RuntimeException('No image has been started'); } $this->eps .= "%%TRAILER\nend restore\n%%EOF"; $blob = $this->eps; $this->eps = null; return $blob; } private function drawPathOperations(Iterable $ops, &$fromX, &$fromY) : string { $pathData = []; foreach ($ops as $op) { switch (true) { case $op instanceof Move: $fromX = $toX = round($op->getX(), self::PRECISION); $fromY = $toY = round($op->getY(), self::PRECISION); $pathData[] = sprintf('%s %s m', $toX, $toY); break; case $op instanceof Line: $fromX = $toX = round($op->getX(), self::PRECISION); $fromY = $toY = round($op->getY(), self::PRECISION); $pathData[] = sprintf('%s %s l', $toX, $toY); break; case $op instanceof EllipticArc: $pathData[] = $this->drawPathOperations($op->toCurves($fromX, $fromY), $fromX, $fromY); break; case $op instanceof Curve: $x1 = round($op->getX1(), self::PRECISION); $y1 = round($op->getY1(), self::PRECISION); $x2 = round($op->getX2(), self::PRECISION); $y2 = round($op->getY2(), self::PRECISION); $fromX = $x3 = round($op->getX3(), self::PRECISION); $fromY = $y3 = round($op->getY3(), self::PRECISION); $pathData[] = sprintf('%s %s %s %s %s %s c', $x1, $y1, $x2, $y2, $x3, $y3); break; case $op instanceof Close: $pathData[] = 'z'; break; default: throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); } } return implode(' ', $pathData); } private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : void { $startColor = $gradient->getStartColor(); $endColor = $gradient->getEndColor(); if ($startColor instanceof Alpha) { $startColor = $startColor->getBaseColor(); } $startColorType = get_class($startColor); if (! in_array($startColorType, [Rgb::class, Cmyk::class, Gray::class])) { $startColorType = Cmyk::class; $startColor = $startColor->toCmyk(); } if (get_class($endColor) !== $startColorType) { switch ($startColorType) { case Cmyk::class: $endColor = $endColor->toCmyk(); break; case Rgb::class: $endColor = $endColor->toRgb(); break; case Gray::class: $endColor = $endColor->toGray(); break; } } $this->eps .= "eoclip\n<<\n"; if ($gradient->getType() === GradientType::RADIAL()) { $this->eps .= " /ShadingType 3\n"; } else { $this->eps .= " /ShadingType 2\n"; } $this->eps .= " /Extend [ true true ]\n" . " /AntiAlias true\n"; switch ($startColorType) { case Cmyk::class: $this->eps .= " /ColorSpace /DeviceCMYK\n"; break; case Rgb::class: $this->eps .= " /ColorSpace /DeviceRGB\n"; break; case Gray::class: $this->eps .= " /ColorSpace /DeviceGray\n"; break; } switch ($gradient->getType()) { case GradientType::HORIZONTAL(): $this->eps .= sprintf( " /Coords [ %s %s %s %s ]\n", round($x, self::PRECISION), round($y, self::PRECISION), round($x + $width, self::PRECISION), round($y, self::PRECISION) ); break; case GradientType::VERTICAL(): $this->eps .= sprintf( " /Coords [ %s %s %s %s ]\n", round($x, self::PRECISION), round($y, self::PRECISION), round($x, self::PRECISION), round($y + $height, self::PRECISION) ); break; case GradientType::DIAGONAL(): $this->eps .= sprintf( " /Coords [ %s %s %s %s ]\n", round($x, self::PRECISION), round($y, self::PRECISION), round($x + $width, self::PRECISION), round($y + $height, self::PRECISION) ); break; case GradientType::INVERSE_DIAGONAL(): $this->eps .= sprintf( " /Coords [ %s %s %s %s ]\n", round($x, self::PRECISION), round($y + $height, self::PRECISION), round($x + $width, self::PRECISION), round($y, self::PRECISION) ); break; case GradientType::RADIAL(): $centerX = ($x + $width) / 2; $centerY = ($y + $height) / 2; $this->eps .= sprintf( " /Coords [ %s %s 0 %s %s %s ]\n", round($centerX, self::PRECISION), round($centerY, self::PRECISION), round($centerX, self::PRECISION), round($centerY, self::PRECISION), round(max($width, $height) / 2, self::PRECISION) ); break; } $this->eps .= " /Function\n" . " <<\n" . " /FunctionType 2\n" . " /Domain [ 0 1 ]\n" . sprintf(" /C0 [ %s ]\n", $this->getColorString($startColor)) . sprintf(" /C1 [ %s ]\n", $this->getColorString($endColor)) . " /N 1\n" . " >>\n>>\nshfill\nQ\n"; } private function getColorSetString(ColorInterface $color) : string { if ($color instanceof Rgb) { return $this->getColorString($color) . ' rgb'; } if ($color instanceof Cmyk) { return $this->getColorString($color) . ' cmyk'; } if ($color instanceof Gray) { return $this->getColorString($color) . ' gray'; } return $this->getColorSetString($color->toCmyk()); } private function getColorString(ColorInterface $color) : string { if ($color instanceof Rgb) { return sprintf('%s %s %s', $color->getRed() / 255, $color->getGreen() / 255, $color->getBlue() / 255); } if ($color instanceof Cmyk) { return sprintf( '%s %s %s %s', $color->getCyan() / 100, $color->getMagenta() / 100, $color->getYellow() / 100, $color->getBlack() / 100 ); } if ($color instanceof Gray) { return sprintf('%s', $color->getGray() / 100); } return $this->getColorString($color->toCmyk()); } } src/Renderer/Image/SvgImageBackEnd.php000077700000030502151323635260013563 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Image; use BaconQrCode\Exception\RuntimeException; use BaconQrCode\Renderer\Color\Alpha; use BaconQrCode\Renderer\Color\ColorInterface; use BaconQrCode\Renderer\Path\Close; use BaconQrCode\Renderer\Path\Curve; use BaconQrCode\Renderer\Path\EllipticArc; use BaconQrCode\Renderer\Path\Line; use BaconQrCode\Renderer\Path\Move; use BaconQrCode\Renderer\Path\Path; use BaconQrCode\Renderer\RendererStyle\Gradient; use BaconQrCode\Renderer\RendererStyle\GradientType; use XMLWriter; final class SvgImageBackEnd implements ImageBackEndInterface { private const PRECISION = 3; /** * @var XMLWriter|null */ private $xmlWriter; /** * @var int[]|null */ private $stack; /** * @var int|null */ private $currentStack; /** * @var int|null */ private $gradientCount; public function __construct() { if (! class_exists(XMLWriter::class)) { throw new RuntimeException('You need to install the libxml extension to use this back end'); } } public function new(int $size, ColorInterface $backgroundColor) : void { $this->xmlWriter = new XMLWriter(); $this->xmlWriter->openMemory(); $this->xmlWriter->startDocument('1.0', 'UTF-8'); $this->xmlWriter->startElement('svg'); $this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg'); $this->xmlWriter->writeAttribute('version', '1.1'); $this->xmlWriter->writeAttribute('width', (string) $size); $this->xmlWriter->writeAttribute('height', (string) $size); $this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size); $this->gradientCount = 0; $this->currentStack = 0; $this->stack[0] = 0; $alpha = 1; if ($backgroundColor instanceof Alpha) { $alpha = $backgroundColor->getAlpha() / 100; } if (0 === $alpha) { return; } $this->xmlWriter->startElement('rect'); $this->xmlWriter->writeAttribute('x', '0'); $this->xmlWriter->writeAttribute('y', '0'); $this->xmlWriter->writeAttribute('width', (string) $size); $this->xmlWriter->writeAttribute('height', (string) $size); $this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor)); if ($alpha < 1) { $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha); } $this->xmlWriter->endElement(); } public function scale(float $size) : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $this->xmlWriter->startElement('g'); $this->xmlWriter->writeAttribute( 'transform', sprintf('scale(%s)', round($size, self::PRECISION)) ); ++$this->stack[$this->currentStack]; } public function translate(float $x, float $y) : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $this->xmlWriter->startElement('g'); $this->xmlWriter->writeAttribute( 'transform', sprintf('translate(%s,%s)', round($x, self::PRECISION), round($y, self::PRECISION)) ); ++$this->stack[$this->currentStack]; } public function rotate(int $degrees) : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $this->xmlWriter->startElement('g'); $this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees)); ++$this->stack[$this->currentStack]; } public function push() : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $this->xmlWriter->startElement('g'); $this->stack[] = 1; ++$this->currentStack; } public function pop() : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) { $this->xmlWriter->endElement(); } array_pop($this->stack); --$this->currentStack; } public function drawPathWithColor(Path $path, ColorInterface $color) : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $alpha = 1; if ($color instanceof Alpha) { $alpha = $color->getAlpha() / 100; } $this->startPathElement($path); $this->xmlWriter->writeAttribute('fill', $this->getColorString($color)); if ($alpha < 1) { $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha); } $this->xmlWriter->endElement(); } public function drawPathWithGradient( Path $path, Gradient $gradient, float $x, float $y, float $width, float $height ) : void { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height); $this->startPathElement($path); $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')'); $this->xmlWriter->endElement(); } public function done() : string { if (null === $this->xmlWriter) { throw new RuntimeException('No image has been started'); } foreach ($this->stack as $openElements) { for ($i = $openElements; $i > 0; --$i) { $this->xmlWriter->endElement(); } } $this->xmlWriter->endDocument(); $blob = $this->xmlWriter->outputMemory(true); $this->xmlWriter = null; $this->stack = null; $this->currentStack = null; $this->gradientCount = null; return $blob; } private function startPathElement(Path $path) : void { $pathData = []; foreach ($path as $op) { switch (true) { case $op instanceof Move: $pathData[] = sprintf( 'M%s %s', round($op->getX(), self::PRECISION), round($op->getY(), self::PRECISION) ); break; case $op instanceof Line: $pathData[] = sprintf( 'L%s %s', round($op->getX(), self::PRECISION), round($op->getY(), self::PRECISION) ); break; case $op instanceof EllipticArc: $pathData[] = sprintf( 'A%s %s %s %u %u %s %s', round($op->getXRadius(), self::PRECISION), round($op->getYRadius(), self::PRECISION), round($op->getXAxisAngle(), self::PRECISION), $op->isLargeArc(), $op->isSweep(), round($op->getX(), self::PRECISION), round($op->getY(), self::PRECISION) ); break; case $op instanceof Curve: $pathData[] = sprintf( 'C%s %s %s %s %s %s', round($op->getX1(), self::PRECISION), round($op->getY1(), self::PRECISION), round($op->getX2(), self::PRECISION), round($op->getY2(), self::PRECISION), round($op->getX3(), self::PRECISION), round($op->getY3(), self::PRECISION) ); break; case $op instanceof Close: $pathData[] = 'Z'; break; default: throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); } } $this->xmlWriter->startElement('path'); $this->xmlWriter->writeAttribute('fill-rule', 'evenodd'); $this->xmlWriter->writeAttribute('d', implode('', $pathData)); } private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string { $this->xmlWriter->startElement('defs'); $startColor = $gradient->getStartColor(); $endColor = $gradient->getEndColor(); if ($gradient->getType() === GradientType::RADIAL()) { $this->xmlWriter->startElement('radialGradient'); } else { $this->xmlWriter->startElement('linearGradient'); } $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse'); switch ($gradient->getType()) { case GradientType::HORIZONTAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); break; case GradientType::VERTICAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); break; case GradientType::DIAGONAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION)); break; case GradientType::INVERSE_DIAGONAL(): $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION)); $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION)); $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION)); $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION)); break; case GradientType::RADIAL(): $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION)); $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION)); $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION)); break; } $id = sprintf('g%d', ++$this->gradientCount); $this->xmlWriter->writeAttribute('id', $id); $this->xmlWriter->startElement('stop'); $this->xmlWriter->writeAttribute('offset', '0%'); $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor)); if ($startColor instanceof Alpha) { $this->xmlWriter->writeAttribute('stop-opacity', $startColor->getAlpha()); } $this->xmlWriter->endElement(); $this->xmlWriter->startElement('stop'); $this->xmlWriter->writeAttribute('offset', '100%'); $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor)); if ($endColor instanceof Alpha) { $this->xmlWriter->writeAttribute('stop-opacity', $endColor->getAlpha()); } $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); $this->xmlWriter->endElement(); return $id; } private function getColorString(ColorInterface $color) : string { $color = $color->toRgb(); return sprintf( '#%02x%02x%02x', $color->getRed(), $color->getGreen(), $color->getBlue() ); } } src/Renderer/Image/ImageBackEndInterface.php000077700000004724151323635260014733 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Image; use BaconQrCode\Exception\RuntimeException; use BaconQrCode\Renderer\Color\ColorInterface; use BaconQrCode\Renderer\Path\Path; use BaconQrCode\Renderer\RendererStyle\Gradient; /** * Interface for back ends able to to produce path based images. */ interface ImageBackEndInterface { /** * Starts a new image. * * If a previous image was already started, previous data get erased. */ public function new(int $size, ColorInterface $backgroundColor) : void; /** * Transforms all following drawing operation coordinates by scaling them by a given factor. * * @throws RuntimeException if no image was started yet. */ public function scale(float $size) : void; /** * Transforms all following drawing operation coordinates by translating them by a given amount. * * @throws RuntimeException if no image was started yet. */ public function translate(float $x, float $y) : void; /** * Transforms all following drawing operation coordinates by rotating them by a given amount. * * @throws RuntimeException if no image was started yet. */ public function rotate(int $degrees) : void; /** * Pushes the current coordinate transformation onto a stack. * * @throws RuntimeException if no image was started yet. */ public function push() : void; /** * Pops the last coordinate transformation from a stack. * * @throws RuntimeException if no image was started yet. */ public function pop() : void; /** * Draws a path with a given color. * * @throws RuntimeException if no image was started yet. */ public function drawPathWithColor(Path $path, ColorInterface $color) : void; /** * Draws a path with a given gradient which spans the box described by the position and size. * * @throws RuntimeException if no image was started yet. */ public function drawPathWithGradient( Path $path, Gradient $gradient, float $x, float $y, float $width, float $height ) : void; /** * Ends the image drawing operation and returns the resulting blob. * * This should reset the state of the back end and thus this method should only be callable once per image. * * @throws RuntimeException if no image was started yet. */ public function done() : string; } src/Renderer/Image/ImagickImageBackEnd.php000077700000024401151323635260014371 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Image; use BaconQrCode\Exception\RuntimeException; use BaconQrCode\Renderer\Color\Alpha; use BaconQrCode\Renderer\Color\Cmyk; use BaconQrCode\Renderer\Color\ColorInterface; use BaconQrCode\Renderer\Color\Gray; use BaconQrCode\Renderer\Color\Rgb; use BaconQrCode\Renderer\Path\Close; use BaconQrCode\Renderer\Path\Curve; use BaconQrCode\Renderer\Path\EllipticArc; use BaconQrCode\Renderer\Path\Line; use BaconQrCode\Renderer\Path\Move; use BaconQrCode\Renderer\Path\Path; use BaconQrCode\Renderer\RendererStyle\Gradient; use BaconQrCode\Renderer\RendererStyle\GradientType; use Imagick; use ImagickDraw; use ImagickPixel; final class ImagickImageBackEnd implements ImageBackEndInterface { /** * @var string */ private $imageFormat; /** * @var int */ private $compressionQuality; /** * @var Imagick|null */ private $image; /** * @var ImagickDraw|null */ private $draw; /** * @var int|null */ private $gradientCount; /** * @var TransformationMatrix[]|null */ private $matrices; /** * @var int|null */ private $matrixIndex; public function __construct(string $imageFormat = 'png', int $compressionQuality = 100) { if (! class_exists(Imagick::class)) { throw new RuntimeException('You need to install the imagick extension to use this back end'); } $this->imageFormat = $imageFormat; $this->compressionQuality = $compressionQuality; } public function new(int $size, ColorInterface $backgroundColor) : void { $this->image = new Imagick(); $this->image->newImage($size, $size, $this->getColorPixel($backgroundColor)); $this->image->setImageFormat($this->imageFormat); $this->image->setCompressionQuality($this->compressionQuality); $this->draw = new ImagickDraw(); $this->gradientCount = 0; $this->matrices = [new TransformationMatrix()]; $this->matrixIndex = 0; } public function scale(float $size) : void { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->draw->scale($size, $size); $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] ->multiply(TransformationMatrix::scale($size)); } public function translate(float $x, float $y) : void { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->draw->translate($x, $y); $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] ->multiply(TransformationMatrix::translate($x, $y)); } public function rotate(int $degrees) : void { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->draw->rotate($degrees); $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex] ->multiply(TransformationMatrix::rotate($degrees)); } public function push() : void { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->draw->push(); $this->matrices[++$this->matrixIndex] = $this->matrices[$this->matrixIndex - 1]; } public function pop() : void { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->draw->pop(); unset($this->matrices[$this->matrixIndex--]); } public function drawPathWithColor(Path $path, ColorInterface $color) : void { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->draw->setFillColor($this->getColorPixel($color)); $this->drawPath($path); } public function drawPathWithGradient( Path $path, Gradient $gradient, float $x, float $y, float $width, float $height ) : void { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->draw->setFillPatternURL('#' . $this->createGradientFill($gradient, $x, $y, $width, $height)); $this->drawPath($path); } public function done() : string { if (null === $this->draw) { throw new RuntimeException('No image has been started'); } $this->image->drawImage($this->draw); $blob = $this->image->getImageBlob(); $this->draw->clear(); $this->image->clear(); $this->draw = null; $this->image = null; $this->gradientCount = null; return $blob; } private function drawPath(Path $path) : void { $this->draw->pathStart(); foreach ($path as $op) { switch (true) { case $op instanceof Move: $this->draw->pathMoveToAbsolute($op->getX(), $op->getY()); break; case $op instanceof Line: $this->draw->pathLineToAbsolute($op->getX(), $op->getY()); break; case $op instanceof EllipticArc: $this->draw->pathEllipticArcAbsolute( $op->getXRadius(), $op->getYRadius(), $op->getXAxisAngle(), $op->isLargeArc(), $op->isSweep(), $op->getX(), $op->getY() ); break; case $op instanceof Curve: $this->draw->pathCurveToAbsolute( $op->getX1(), $op->getY1(), $op->getX2(), $op->getY2(), $op->getX3(), $op->getY3() ); break; case $op instanceof Close: $this->draw->pathClose(); break; default: throw new RuntimeException('Unexpected draw operation: ' . get_class($op)); } } $this->draw->pathFinish(); } private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string { list($width, $height) = $this->matrices[$this->matrixIndex]->apply($x + $width, $y + $height); list($x, $y) = $this->matrices[$this->matrixIndex]->apply($x, $y); $width -= $x; $height -= $y; $startColor = $this->getColorPixel($gradient->getStartColor())->getColorAsString(); $endColor = $this->getColorPixel($gradient->getEndColor())->getColorAsString(); $gradientImage = new Imagick(); switch ($gradient->getType()) { case GradientType::HORIZONTAL(): $gradientImage->newPseudoImage((int) $height, (int) $width, sprintf( 'gradient:%s-%s', $startColor, $endColor )); $gradientImage->rotateImage('transparent', -90); break; case GradientType::VERTICAL(): $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf( 'gradient:%s-%s', $startColor, $endColor )); break; case GradientType::DIAGONAL(): case GradientType::INVERSE_DIAGONAL(): $gradientImage->newPseudoImage((int) ($width * sqrt(2)), (int) ($height * sqrt(2)), sprintf( 'gradient:%s-%s', $startColor, $endColor )); if (GradientType::DIAGONAL() === $gradient->getType()) { $gradientImage->rotateImage('transparent', -45); } else { $gradientImage->rotateImage('transparent', -135); } $rotatedWidth = $gradientImage->getImageWidth(); $rotatedHeight = $gradientImage->getImageHeight(); $gradientImage->setImagePage($rotatedWidth, $rotatedHeight, 0, 0); $gradientImage->cropImage( intdiv($rotatedWidth, 2) - 2, intdiv($rotatedHeight, 2) - 2, intdiv($rotatedWidth, 4) + 1, intdiv($rotatedWidth, 4) + 1 ); break; case GradientType::RADIAL(): $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf( 'radial-gradient:%s-%s', $startColor, $endColor )); break; } $id = sprintf('g%d', ++$this->gradientCount); $this->draw->pushPattern($id, 0, 0, $x + $width, $y + $height); $this->draw->composite(Imagick::COMPOSITE_COPY, $x, $y, $width, $height, $gradientImage); $this->draw->popPattern(); return $id; } private function getColorPixel(ColorInterface $color) : ImagickPixel { $alpha = 100; if ($color instanceof Alpha) { $alpha = $color->getAlpha(); $color = $color->getBaseColor(); } if ($color instanceof Rgb) { return new ImagickPixel(sprintf( 'rgba(%d, %d, %d, %F)', $color->getRed(), $color->getGreen(), $color->getBlue(), $alpha / 100 )); } if ($color instanceof Cmyk) { return new ImagickPixel(sprintf( 'cmyka(%d, %d, %d, %d, %F)', $color->getCyan(), $color->getMagenta(), $color->getYellow(), $color->getBlack(), $alpha / 100 )); } if ($color instanceof Gray) { return new ImagickPixel(sprintf( 'graya(%d%%, %F)', $color->getGray(), $alpha / 100 )); } return $this->getColorPixel(new Alpha($alpha, $color->toRgb())); } } src/Renderer/Image/.htaccess000077700000000177151323635260011743 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/.htaccess000077700000000177151323635260010721 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/Module/DotsModule.php000077700000003434151323635260013137 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Module; use BaconQrCode\Encoder\ByteMatrix; use BaconQrCode\Exception\InvalidArgumentException; use BaconQrCode\Renderer\Path\Path; /** * Renders individual modules as dots. */ final class DotsModule implements ModuleInterface { public const LARGE = 1; public const MEDIUM = .8; public const SMALL = .6; /** * @var float */ private $size; public function __construct(float $size) { if ($size <= 0 || $size > 1) { throw new InvalidArgumentException('Size must between 0 (exclusive) and 1 (inclusive)'); } $this->size = $size; } public function createPath(ByteMatrix $matrix) : Path { $width = $matrix->getWidth(); $height = $matrix->getHeight(); $path = new Path(); $halfSize = $this->size / 2; $margin = (1 - $this->size) / 2; for ($y = 0; $y < $height; ++$y) { for ($x = 0; $x < $width; ++$x) { if (! $matrix->get($x, $y)) { continue; } $pathX = $x + $margin; $pathY = $y + $margin; $path = $path ->move($pathX + $this->size, $pathY + $halfSize) ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY + $this->size) ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX, $pathY + $halfSize) ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY) ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $this->size, $pathY + $halfSize) ->close() ; } } return $path; } } src/Renderer/Module/EdgeIterator/Edge.php000077700000003771151323635260014306 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Module\EdgeIterator; final class Edge { /** * @var bool */ private $positive; /** * @var array<int[]> */ private $points = []; /** * @var array<int[]>|null */ private $simplifiedPoints; /** * @var int */ private $minX = PHP_INT_MAX; /** * @var int */ private $minY = PHP_INT_MAX; /** * @var int */ private $maxX = -1; /** * @var int */ private $maxY = -1; public function __construct(bool $positive) { $this->positive = $positive; } public function addPoint(int $x, int $y) : void { $this->points[] = [$x, $y]; $this->minX = min($this->minX, $x); $this->minY = min($this->minY, $y); $this->maxX = max($this->maxX, $x); $this->maxY = max($this->maxY, $y); } public function isPositive() : bool { return $this->positive; } /** * @return array<int[]> */ public function getPoints() : array { return $this->points; } public function getMaxX() : int { return $this->maxX; } public function getSimplifiedPoints() : array { if (null !== $this->simplifiedPoints) { return $this->simplifiedPoints; } $points = []; $length = count($this->points); for ($i = 0; $i < $length; ++$i) { $previousPoint = $this->points[(0 === $i ? $length : $i) - 1]; $nextPoint = $this->points[($length - 1 === $i ? -1 : $i) + 1]; $currentPoint = $this->points[$i]; if (($previousPoint[0] === $currentPoint[0] && $currentPoint[0] === $nextPoint[0]) || ($previousPoint[1] === $currentPoint[1] && $currentPoint[1] === $nextPoint[1]) ) { continue; } $points[] = $currentPoint; } return $this->simplifiedPoints = $points; } } src/Renderer/Module/EdgeIterator/EdgeIterator.php000077700000007136151323635260016017 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Module\EdgeIterator; use BaconQrCode\Encoder\ByteMatrix; use IteratorAggregate; use Traversable; /** * Edge iterator based on potrace. */ final class EdgeIterator implements IteratorAggregate { /** * @var int[] */ private $bytes = []; /** * @var int */ private $size; /** * @var int */ private $width; /** * @var int */ private $height; public function __construct(ByteMatrix $matrix) { $this->bytes = iterator_to_array($matrix->getBytes()); $this->size = count($this->bytes); $this->width = $matrix->getWidth(); $this->height = $matrix->getHeight(); } /** * @return Edge[] */ public function getIterator() : Traversable { $originalBytes = $this->bytes; $point = $this->findNext(0, 0); while (null !== $point) { $edge = $this->findEdge($point[0], $point[1]); $this->xorEdge($edge); yield $edge; $point = $this->findNext($point[0], $point[1]); } $this->bytes = $originalBytes; } /** * @return int[]|null */ private function findNext(int $x, int $y) : ?array { $i = $this->width * $y + $x; while ($i < $this->size && 1 !== $this->bytes[$i]) { ++$i; } if ($i < $this->size) { return $this->pointOf($i); } return null; } private function findEdge(int $x, int $y) : Edge { $edge = new Edge($this->isSet($x, $y)); $startX = $x; $startY = $y; $dirX = 0; $dirY = 1; while (true) { $edge->addPoint($x, $y); $x += $dirX; $y += $dirY; if ($x === $startX && $y === $startY) { break; } $left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2); $right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2); if ($right && ! $left) { $tmp = $dirX; $dirX = -$dirY; $dirY = $tmp; } elseif ($right) { $tmp = $dirX; $dirX = -$dirY; $dirY = $tmp; } elseif (! $left) { $tmp = $dirX; $dirX = $dirY; $dirY = -$tmp; } } return $edge; } private function xorEdge(Edge $path) : void { $points = $path->getPoints(); $y1 = $points[0][1]; $length = count($points); $maxX = $path->getMaxX(); for ($i = 1; $i < $length; ++$i) { $y = $points[$i][1]; if ($y === $y1) { continue; } $x = $points[$i][0]; $minY = min($y1, $y); for ($j = $x; $j < $maxX; ++$j) { $this->flip($j, $minY); } $y1 = $y; } } private function isSet(int $x, int $y) : bool { return ( $x >= 0 && $x < $this->width && $y >= 0 && $y < $this->height ) && 1 === $this->bytes[$this->width * $y + $x]; } /** * @return int[] */ private function pointOf(int $i) : array { $y = intdiv($i, $this->width); return [$i - $y * $this->width, $y]; } private function flip(int $x, int $y) : void { $this->bytes[$this->width * $y + $x] = ( $this->isSet($x, $y) ? 0 : 1 ); } } src/Renderer/Module/EdgeIterator/.htaccess000077700000000177151323635260014524 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Renderer/Module/RoundnessModule.php000077700000010574151323635260014211 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Module; use BaconQrCode\Encoder\ByteMatrix; use BaconQrCode\Exception\InvalidArgumentException; use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator; use BaconQrCode\Renderer\Path\Path; /** * Rounds the corners of module groups. */ final class RoundnessModule implements ModuleInterface { public const STRONG = 1; public const MEDIUM = .5; public const SOFT = .25; /** * @var float */ private $intensity; public function __construct(float $intensity) { if ($intensity <= 0 || $intensity > 1) { throw new InvalidArgumentException('Intensity must between 0 (exclusive) and 1 (inclusive)'); } $this->intensity = $intensity / 2; } public function createPath(ByteMatrix $matrix) : Path { $path = new Path(); foreach (new EdgeIterator($matrix) as $edge) { $points = $edge->getSimplifiedPoints(); $length = count($points); $currentPoint = $points[0]; $nextPoint = $points[1]; $horizontal = ($currentPoint[1] === $nextPoint[1]); if ($horizontal) { $right = $nextPoint[0] > $currentPoint[0]; $path = $path->move( $currentPoint[0] + ($right ? $this->intensity : -$this->intensity), $currentPoint[1] ); } else { $up = $nextPoint[0] < $currentPoint[0]; $path = $path->move( $currentPoint[0], $currentPoint[1] + ($up ? -$this->intensity : $this->intensity) ); } for ($i = 1; $i <= $length; ++$i) { if ($i === $length) { $previousPoint = $points[$length - 1]; $currentPoint = $points[0]; $nextPoint = $points[1]; } else { $previousPoint = $points[(0 === $i ? $length : $i) - 1]; $currentPoint = $points[$i]; $nextPoint = $points[($length - 1 === $i ? -1 : $i) + 1]; } $horizontal = ($previousPoint[1] === $currentPoint[1]); if ($horizontal) { $right = $previousPoint[0] < $currentPoint[0]; $up = $nextPoint[1] < $currentPoint[1]; $sweep = ($up xor $right); if ($this->intensity < 0.5 || ($right && $previousPoint[0] !== $currentPoint[0] - 1) || (! $right && $previousPoint[0] - 1 !== $currentPoint[0]) ) { $path = $path->line( $currentPoint[0] + ($right ? -$this->intensity : $this->intensity), $currentPoint[1] ); } $path = $path->ellipticArc( $this->intensity, $this->intensity, 0, false, $sweep, $currentPoint[0], $currentPoint[1] + ($up ? -$this->intensity : $this->intensity) ); } else { $up = $previousPoint[1] > $currentPoint[1]; $right = $nextPoint[0] > $currentPoint[0]; $sweep = ! ($up xor $right); if ($this->intensity < 0.5 || ($up && $previousPoint[1] !== $currentPoint[1] + 1) || (! $up && $previousPoint[0] + 1 !== $currentPoint[0]) ) { $path = $path->line( $currentPoint[0], $currentPoint[1] + ($up ? $this->intensity : -$this->intensity) ); } $path = $path->ellipticArc( $this->intensity, $this->intensity, 0, false, $sweep, $currentPoint[0] + ($right ? $this->intensity : -$this->intensity), $currentPoint[1] ); } } $path = $path->close(); } return $path; } } src/Renderer/Module/SquareModule.php000077700000002046151323635260013464 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Module; use BaconQrCode\Encoder\ByteMatrix; use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator; use BaconQrCode\Renderer\Path\Path; /** * Groups modules together to a single path. */ final class SquareModule implements ModuleInterface { /** * @var self|null */ private static $instance; private function __construct() { } public static function instance() : self { return self::$instance ?: self::$instance = new self(); } public function createPath(ByteMatrix $matrix) : Path { $path = new Path(); foreach (new EdgeIterator($matrix) as $edge) { $points = $edge->getSimplifiedPoints(); $length = count($points); $path = $path->move($points[0][0], $points[0][1]); for ($i = 1; $i < $length; ++$i) { $path = $path->line($points[$i][0], $points[$i][1]); } $path = $path->close(); } return $path; } } src/Renderer/Module/ModuleInterface.php000077700000000753151323635260014127 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Renderer\Module; use BaconQrCode\Encoder\ByteMatrix; use BaconQrCode\Renderer\Path\Path; /** * Interface describing how modules should be rendered. * * A module always receives a byte matrix (with values either being 1 or 0). It returns a path, where the origin * coordinate (0, 0) equals the top left corner of the first matrix value. */ interface ModuleInterface { public function createPath(ByteMatrix $matrix) : Path; } src/Renderer/Module/.htaccess000077700000000177151323635260012146 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/Exception/RuntimeException.php000077700000000235151323635260013321 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Exception; final class RuntimeException extends \RuntimeException implements ExceptionInterface { } src/Exception/UnexpectedValueException.php000077700000000255151323635260015001 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Exception; final class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface { } src/Exception/InvalidArgumentException.php000077700000000255151323635260014771 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Exception; final class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface { } src/Exception/OutOfBoundsException.php000077700000000245151323635260014106 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Exception; final class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface { } src/Exception/WriterException.php000077700000000234151323635260013151 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Exception; final class WriterException extends \RuntimeException implements ExceptionInterface { } src/Exception/ExceptionInterface.php000077700000000207151323635260013575 0ustar00<?php declare(strict_types = 1); namespace BaconQrCode\Exception; use Throwable; interface ExceptionInterface extends Throwable { } src/Exception/.htaccess000077700000000177151323635260011111 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>src/.htaccess000077700000000177151323635260007153 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>README.md000077700000003224151323635260006041 0ustar00# QR Code generator [](http://travis-ci.org/Bacon/BaconQrCode) [](https://coveralls.io/github/Bacon/BaconQrCode?branch=master) [](https://packagist.org/packages/bacon/bacon-qr-code) [](https://packagist.org/packages/bacon/bacon-qr-code) [](https://packagist.org/packages/bacon/bacon-qr-code) ## Introduction BaconQrCode is a port of QR code portion of the ZXing library. It currently only features the encoder part, but could later receive the decoder part as well. As the Reed Solomon codec implementation of the ZXing library performs quite slow in PHP, it was exchanged with the implementation by Phil Karn. ## Example usage ```php use BaconQrCode\Renderer\ImageRenderer; use BaconQrCode\Renderer\Image\ImagickImageBackEnd; use BaconQrCode\Renderer\RendererStyle\RendererStyle; use BaconQrCode\Writer; $renderer = new ImageRenderer( new RendererStyle(400), new ImagickImageBackEnd() ); $writer = new Writer($renderer); $writer->writeFile('Hello World!', 'qrcode.png'); ``` ## Available image renderer back ends BaconQrCode comes with multiple back ends for rendering images. Currently included are the following: - `ImagickImageBackEnd`: renders raster images using the Imagick library - `SvgImageBackEnd`: renders SVG files using XMLWriter - `EpsImageBackEnd`: renders EPS files LICENSE000077700000002434151323635260005571 0ustar00Copyright (c) 2017, Ben Scholzen 'DASPRiD' All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. CHANGELOG.md000077700000002227151323635260006375 0ustar00# Changelog All notable changes to this project will be documented in this file, in reverse chronological order by release. ## 2.0.2 - 2020-07-30 ### Changed - [#71](https://github.com/Bacon/BaconQrCode/issues/71) Upgrade phpunit. - [#71](https://github.com/Bacon/BaconQrCode/issues/71) Allow tests in vendor bundles for Debian packaging. - [#71](https://github.com/Bacon/BaconQrCode/issues/71) Update TravisCI config file. ## 2.0.1 - 2020-07-14 ### Fixed - [#69](https://github.com/Bacon/BaconQrCode/pull/69) SimpleCircleEye Class not working properly. ## 2.0.0 - 2018-04-25 ### Added - [#25](https://github.com/Bacon/BaconQrCode/pull/25) allows for setting a more compact text output - CHANGELOG.md added (how meta) - Allows more complex shapes for modules - Allows setting a gradient for the foreground - Allows transparent backgrounds and alpha channel on all colors ### Changed - Minimum PHP version changed to 7.1 - Imagick renderer now allows setting different output formats - New optimized SVG renderer ### Deprecated - Nothing. ### Removed - Legacy ZF module support removed ### Fixed - Non-release files are excluded from composer packages composer.json000077700000001507151323635260007306 0ustar00{ "name": "bacon/bacon-qr-code", "description": "BaconQrCode is a QR code generator for PHP.", "license" : "BSD-2-Clause", "homepage": "https://github.com/Bacon/BaconQrCode", "require": { "php": "^7.1 || ^8.0", "ext-iconv": "*", "dasprid/enum": "^1.0.3" }, "suggest": { "ext-imagick": "to generate QR code images" }, "authors": [ { "name": "Ben Scholzen 'DASPRiD'", "email": "mail@dasprids.de", "homepage": "https://dasprids.de/", "role": "Developer" } ], "autoload": { "psr-4": { "BaconQrCode\\": "src/" } }, "require-dev": { "phpunit/phpunit": "^7 | ^8 | ^9", "squizlabs/php_codesniffer": "^3.4", "phly/keep-a-changelog": "^1.4" } } .htaccess000077700000000177151323635260006364 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>test/Common/BitArrayTest.php000077700000013645151323635260012067 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\BitArray; use PHPUnit\Framework\TestCase; use PHPUnit\Runner\Version as PHPUnitVersion; final class BitArrayTest extends TestCase { private function getPhpUnitMajorVersion(): int { return (int) explode('.', PHPUnitVersion::id())[0]; } public function testGetSet() : void { $array = new BitArray(33); for ($i = 0; $i < 33; ++$i) { $this->assertFalse($array->get($i)); $array->set($i); $this->assertTrue($array->get($i)); } } public function testGetNextSet1() : void { $array = new BitArray(32); for ($i = 0; $i < $array->getSize(); ++$i) { if ($this->getPhpUnitMajorVersion() === 7) { $this->assertEquals($i, 32, '', $array->getNextSet($i)); } else { $this->assertEqualsWithDelta($i, 32, $array->getNextSet($i)); } } $array = new BitArray(33); for ($i = 0; $i < $array->getSize(); ++$i) { if ($this->getPhpUnitMajorVersion() === 7) { $this->assertEquals($i, 33, '', $array->getNextSet($i)); } else { $this->assertEqualsWithDelta($i, 33, $array->getNextSet($i)); } } } public function testGetNextSet2() : void { $array = new BitArray(33); for ($i = 0; $i < $array->getSize(); ++$i) { if ($this->getPhpUnitMajorVersion() === 7) { $this->assertEquals($i, $i <= 31 ? 31 : 33, '', $array->getNextSet($i)); } else { $this->assertEqualsWithDelta($i, $i <= 31 ? 31 : 33, $array->getNextSet($i)); } } $array = new BitArray(33); for ($i = 0; $i < $array->getSize(); ++$i) { if ($this->getPhpUnitMajorVersion() === 7) { $this->assertEquals($i, 32, '', $array->getNextSet($i)); } else { $this->assertEqualsWithDelta($i, 32, $array->getNextSet($i)); } } } public function testGetNextSet3() : void { $array = new BitArray(63); $array->set(31); $array->set(32); for ($i = 0; $i < $array->getSize(); ++$i) { if ($i <= 31) { $expected = 31; } elseif ($i <= 32) { $expected = 32; } else { $expected = 63; } if ($this->getPhpUnitMajorVersion() === 7) { $this->assertEquals($i, $expected, '', $array->getNextSet($i)); } else { $this->assertEqualsWithDelta($i, $expected, $array->getNextSet($i)); } } } public function testGetNextSet4() : void { $array = new BitArray(63); $array->set(33); $array->set(40); for ($i = 0; $i < $array->getSize(); ++$i) { if ($i <= 33) { $expected = 33; } elseif ($i <= 40) { $expected = 40; } else { $expected = 63; } if ($this->getPhpUnitMajorVersion() === 7) { $this->assertEquals($i, $expected, '', $array->getNextSet($i)); } else { $this->assertEqualsWithDelta($i, $expected, $array->getNextSet($i)); } } } public function testGetNextSet5() : void { mt_srand(0xdeadbeef, MT_RAND_PHP); for ($i = 0; $i < 10; ++$i) { $array = new BitArray(mt_rand(1, 100)); $numSet = mt_rand(0, 19); for ($j = 0; $j < $numSet; ++$j) { $array->set(mt_rand(0, $array->getSize() - 1)); } $numQueries = mt_rand(0, 19); for ($j = 0; $j < $numQueries; ++$j) { $query = mt_rand(0, $array->getSize() - 1); $expected = $query; while ($expected < $array->getSize() && ! $array->get($expected)) { ++$expected; } $actual = $array->getNextSet($query); if ($actual !== $expected) { $array->getNextSet($query); } $this->assertEquals($expected, $actual); } } } public function testSetBulk() : void { $array = new BitArray(64); $array->setBulk(32, 0xFFFF0000); for ($i = 0; $i < 48; ++$i) { $this->assertFalse($array->get($i)); } for ($i = 48; $i < 64; ++$i) { $this->assertTrue($array->get($i)); } } public function testClear() : void { $array = new BitArray(32); for ($i = 0; $i < 32; ++$i) { $array->set($i); } $array->clear(); for ($i = 0; $i < 32; ++$i) { $this->assertFalse($array->get($i)); } } public function testGetArray() : void { $array = new BitArray(64); $array->set(0); $array->set(63); $ints = $array->getBitArray(); $this->assertSame(1, $ints[0]); $this->assertSame(0x80000000, $ints[1]); } public function testIsRange() : void { $array = new BitArray(64); $this->assertTrue($array->isRange(0, 64, false)); $this->assertFalse($array->isRange(0, 64, true)); $array->set(32); $this->assertTrue($array->isRange(32, 33, true)); $array->set(31); $this->assertTrue($array->isRange(31, 33, true)); $array->set(34); $this->assertFalse($array->isRange(31, 35, true)); for ($i = 0; $i < 31; ++$i) { $array->set($i); } $this->assertTrue($array->isRange(0, 33, true)); for ($i = 33; $i < 64; ++$i) { $array->set($i); } $this->assertTrue($array->isRange(0, 64, true)); $this->assertFalse($array->isRange(0, 64, false)); } } test/Common/VersionTest.php000077700000004415151323635260011772 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Common\Version; use PHPUnit\Framework\TestCase; class VersionTest extends TestCase { public function versions() : array { $array = []; for ($i = 1; $i <= 40; ++$i) { $array[] = [$i, 4 * $i + 17]; } return $array; } public function decodeInformation() : array { return [ [7, 0x07c94], [12, 0x0c762], [17, 0x1145d], [22, 0x168c9], [27, 0x1b08e], [32, 0x209d5], ]; } /** * @dataProvider versions */ public function testVersionForNumber(int $versionNumber, int $dimension) : void { $version = Version::getVersionForNumber($versionNumber); $this->assertNotNull($version); $this->assertEquals($versionNumber, $version->getVersionNumber()); $this->assertNotNull($version->getAlignmentPatternCenters()); if ($versionNumber > 1) { $this->assertTrue(count($version->getAlignmentPatternCenters()) > 0); } $this->assertEquals($dimension, $version->getDimensionForVersion()); $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::H())); $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::L())); $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::M())); $this->assertNotNull($version->getEcBlocksForLevel(ErrorCorrectionLevel::Q())); $this->assertNotNull($version->buildFunctionPattern()); } /** * @dataProvider versions */ public function testGetProvisionalVersionForDimension(int $versionNumber, int $dimension) : void { $this->assertSame( $versionNumber, Version::getProvisionalVersionForDimension($dimension)->getVersionNumber() ); } /** * @dataProvider decodeInformation */ public function testDecodeVersionInformation(int $expectedVersion, int $mask) : void { $version = Version::decodeVersionInformation($mask); $this->assertNotNull($version); $this->assertSame($expectedVersion, $version->getVersionNumber()); } } test/Common/FormatInformationTest.php000077700000005651151323635260014006 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Common\FormatInformation; use PHPUnit\Framework\TestCase; class FormatInformationTest extends TestCase { private const MASKED_TEST_FORMAT_INFO = 0x2bed; private const UNMAKSED_TEST_FORMAT_INFO = self::MASKED_TEST_FORMAT_INFO ^ 0x5412; public function testBitsDiffering() : void { $this->assertSame(0, FormatInformation::numBitsDiffering(1, 1)); $this->assertSame(1, FormatInformation::numBitsDiffering(0, 2)); $this->assertSame(2, FormatInformation::numBitsDiffering(1, 2)); $this->assertEquals(32, FormatInformation::numBitsDiffering(-1, 0)); } public function testDecode() : void { $expected = FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO, self::MASKED_TEST_FORMAT_INFO ); $this->assertNotNull($expected); $this->assertSame(7, $expected->getDataMask()); $this->assertSame(ErrorCorrectionLevel::Q(), $expected->getErrorCorrectionLevel()); $this->assertEquals( $expected, FormatInformation::decodeFormatInformation( self::UNMAKSED_TEST_FORMAT_INFO, self::MASKED_TEST_FORMAT_INFO ) ); } public function testDecodeWithBitDifference() : void { $expected = FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO, self::MASKED_TEST_FORMAT_INFO ); $this->assertEquals( $expected, FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO ^ 0x1, self::MASKED_TEST_FORMAT_INFO ^ 0x1 ) ); $this->assertEquals( $expected, FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO ^ 0x3, self::MASKED_TEST_FORMAT_INFO ^ 0x3 ) ); $this->assertEquals( $expected, FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO ^ 0x7, self::MASKED_TEST_FORMAT_INFO ^ 0x7 ) ); $this->assertNull( FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO ^ 0xf, self::MASKED_TEST_FORMAT_INFO ^ 0xf ) ); } public function testDecodeWithMisRead() : void { $expected = FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO, self::MASKED_TEST_FORMAT_INFO ); $this->assertEquals( $expected, FormatInformation::decodeFormatInformation( self::MASKED_TEST_FORMAT_INFO ^ 0x3, self::MASKED_TEST_FORMAT_INFO ^ 0xf ) ); } } test/Common/ReedSolomonCodecTest.php000077700000006051151323635260013527 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\ReedSolomonCodec; use PHPUnit\Framework\TestCase; use SplFixedArray; class ReedSolomonTest extends TestCase { public function tabs() : array { return [ [2, 0x7, 1, 1, 1], [3, 0xb, 1, 1, 2], [4, 0x13, 1, 1, 4], [5, 0x25, 1, 1, 6], [6, 0x43, 1, 1, 8], [7, 0x89, 1, 1, 10], [8, 0x11d, 1, 1, 32], ]; } /** * @dataProvider tabs */ public function testCodec(int $symbolSize, int $generatorPoly, int $firstRoot, int $primitive, int $numRoots) : void { mt_srand(0xdeadbeef, MT_RAND_PHP); $blockSize = (1 << $symbolSize) - 1; $dataSize = $blockSize - $numRoots; $codec = new ReedSolomonCodec($symbolSize, $generatorPoly, $firstRoot, $primitive, $numRoots, 0); for ($errors = 0; $errors <= $numRoots / 2; ++$errors) { // Load block with random data and encode $block = SplFixedArray::fromArray(array_fill(0, $blockSize, 0), false); for ($i = 0; $i < $dataSize; ++$i) { $block[$i] = mt_rand(0, $blockSize); } // Make temporary copy $tBlock = clone $block; $parity = SplFixedArray::fromArray(array_fill(0, $numRoots, 0), false); $errorLocations = SplFixedArray::fromArray(array_fill(0, $blockSize, 0), false); $erasures = []; // Create parity $codec->encode($block, $parity); // Copy parity into test blocks for ($i = 0; $i < $numRoots; ++$i) { $block[$i + $dataSize] = $parity[$i]; $tBlock[$i + $dataSize] = $parity[$i]; } // Seed with errors for ($i = 0; $i < $errors; ++$i) { $errorValue = mt_rand(1, $blockSize); do { $errorLocation = mt_rand(0, $blockSize); } while (0 !== $errorLocations[$errorLocation]); $errorLocations[$errorLocation] = 1; if (mt_rand(0, 1)) { $erasures[] = $errorLocation; } $tBlock[$errorLocation] ^= $errorValue; } $erasures = SplFixedArray::fromArray($erasures, false); // Decode the errored block $foundErrors = $codec->decode($tBlock, $erasures); if ($errors > 0 && null === $foundErrors) { $this->assertSame($block, $tBlock, 'Decoder failed to correct errors'); } $this->assertSame($errors, $foundErrors, 'Found errors do not equal expected errors'); for ($i = 0; $i < $foundErrors; ++$i) { if (0 === $errorLocations[$erasures[$i]]) { $this->fail(sprintf('Decoder indicates error in location %d without error', $erasures[$i])); } } $this->assertEquals($block, $tBlock, 'Decoder did not correct errors'); } } } test/Common/BitUtilsTest.php000077700000001420151323635260012075 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\BitUtils; use PHPUnit\Framework\TestCase; class BitUtilsTest extends TestCase { public function testUnsignedRightShift() : void { $this->assertSame(1, BitUtils::unsignedRightShift(1, 0)); $this->assertSame(1, BitUtils::unsignedRightShift(10, 3)); $this->assertSame(536870910, BitUtils::unsignedRightShift(-10, 3)); } public function testNumberOfTrailingZeros() : void { $this->assertSame(32, BitUtils::numberOfTrailingZeros(0)); $this->assertSame(1, BitUtils::numberOfTrailingZeros(10)); $this->assertSame(0, BitUtils::numberOfTrailingZeros(15)); $this->assertSame(2, BitUtils::numberOfTrailingZeros(20)); } } test/Common/ModeTest.php000077700000001026151323635260011224 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\Mode; use PHPUnit\Framework\TestCase; class ModeTest extends TestCase { public function testBitsMatchConstants() : void { $this->assertSame(0x0, Mode::TERMINATOR()->getBits()); $this->assertSame(0x1, Mode::NUMERIC()->getBits()); $this->assertSame(0x2, Mode::ALPHANUMERIC()->getBits()); $this->assertSame(0x4, Mode::BYTE()->getBits()); $this->assertSame(0x8, Mode::KANJI()->getBits()); } } test/Common/ErrorCorrectionLevelTest.php000077700000001417151323635260014455 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Exception\OutOfBoundsException; use PHPUnit\Framework\TestCase; class ErrorCorrectionLevelTest extends TestCase { public function testBitsMatchConstants() : void { $this->assertSame(0x0, ErrorCorrectionLevel::M()->getBits()); $this->assertSame(0x1, ErrorCorrectionLevel::L()->getBits()); $this->assertSame(0x2, ErrorCorrectionLevel::H()->getBits()); $this->assertSame(0x3, ErrorCorrectionLevel::Q()->getBits()); } public function testInvalidErrorCorrectionLevelThrowsException() : void { $this->expectException(OutOfBoundsException::class); ErrorCorrectionLevel::forBits(4); } } test/Common/BitMatrixTest.php000077700000006240151323635260012246 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Common; use BaconQrCode\Common\BitArray; use BaconQrCode\Common\BitMatrix; use PHPUnit\Framework\TestCase; class BitMatrixTest extends TestCase { public function testGetSet() : void { $matrix = new BitMatrix(33); $this->assertEquals(33, $matrix->getHeight()); for ($y = 0; $y < 33; ++$y) { for ($x = 0; $x < 33; ++$x) { if ($y * $x % 3 === 0) { $matrix->set($x, $y); } } } for ($y = 0; $y < 33; $y++) { for ($x = 0; $x < 33; ++$x) { $this->assertSame(0 === $x * $y % 3, $matrix->get($x, $y)); } } } public function testSetRegion() : void { $matrix = new BitMatrix(5); $matrix->setRegion(1, 1, 3, 3); for ($y = 0; $y < 5; ++$y) { for ($x = 0; $x < 5; ++$x) { $this->assertSame($y >= 1 && $y <= 3 && $x >= 1 && $x <= 3, $matrix->get($x, $y)); } } } public function testRectangularMatrix() : void { $matrix = new BitMatrix(75, 20); $this->assertSame(75, $matrix->getWidth()); $this->assertSame(20, $matrix->getHeight()); $matrix->set(10, 0); $matrix->set(11, 1); $matrix->set(50, 2); $matrix->set(51, 3); $matrix->flip(74, 4); $matrix->flip(0, 5); $this->assertTrue($matrix->get(10, 0)); $this->assertTrue($matrix->get(11, 1)); $this->assertTrue($matrix->get(50, 2)); $this->assertTrue($matrix->get(51, 3)); $this->assertTrue($matrix->get(74, 4)); $this->assertTrue($matrix->get(0, 5)); $matrix->flip(50, 2); $matrix->flip(51, 3); $this->assertFalse($matrix->get(50, 2)); $this->assertFalse($matrix->get(51, 3)); } public function testRectangularSetRegion() : void { $matrix = new BitMatrix(320, 240); $this->assertSame(320, $matrix->getWidth()); $this->assertSame(240, $matrix->getHeight()); $matrix->setRegion(105, 22, 80, 12); for ($y = 0; $y < 240; ++$y) { for ($x = 0; $x < 320; ++$x) { $this->assertEquals($y >= 22 && $y < 34 && $x >= 105 && $x < 185, $matrix->get($x, $y)); } } } public function testGetRow() : void { $matrix = new BitMatrix(102, 5); for ($x = 0; $x < 102; ++$x) { if (0 === ($x & 3)) { $matrix->set($x, 2); } } $array1 = $matrix->getRow(2, null); $this->assertSame(102, $array1->getSize()); $array2 = new BitArray(60); $array2 = $matrix->getRow(2, $array2); $this->assertSame(102, $array2->getSize()); $array3 = new BitArray(200); $array3 = $matrix->getRow(2, $array3); $this->assertSame(200, $array3->getSize()); for ($x = 0; $x < 102; ++$x) { $on = (0 === ($x & 3)); $this->assertSame($on, $array1->get($x)); $this->assertSame($on, $array2->get($x)); $this->assertSame($on, $array3->get($x)); } } } test/Common/.htaccess000077700000000177151323635260010573 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>test/Encoder/MatrixUtilTest.php000077700000036101151323635260012573 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Encoder; use BaconQrCode\Common\BitArray; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Common\Version; use BaconQrCode\Encoder\ByteMatrix; use BaconQrCode\Encoder\MatrixUtil; use PHPUnit\Framework\TestCase; use ReflectionClass; use ReflectionMethod; class MatrixUtilTest extends TestCase { /** * @var ReflectionMethod[] */ protected $methods = []; public function setUp() : void { // Hack to be able to test protected methods $reflection = new ReflectionClass(MatrixUtil::class); foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC) as $method) { $method->setAccessible(true); $this->methods[$method->getName()] = $method; } } public function testToString() : void { $matrix = new ByteMatrix(3, 3); $matrix->set(0, 0, 0); $matrix->set(1, 0, 1); $matrix->set(2, 0, 0); $matrix->set(0, 1, 1); $matrix->set(1, 1, 0); $matrix->set(2, 1, 1); $matrix->set(0, 2, -1); $matrix->set(1, 2, -1); $matrix->set(2, 2, -1); $expected = " 0 1 0\n 1 0 1\n \n"; $this->assertSame($expected, (string) $matrix); } public function testClearMatrix() : void { $matrix = new ByteMatrix(2, 2); MatrixUtil::clearMatrix($matrix); $this->assertSame(-1, $matrix->get(0, 0)); $this->assertSame(-1, $matrix->get(1, 0)); $this->assertSame(-1, $matrix->get(0, 1)); $this->assertSame(-1, $matrix->get(1, 1)); } public function testEmbedBasicPatterns1() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); $this->methods['embedBasicPatterns']->invoke( null, Version::getVersionForNumber(1), $matrix ); $expected = " 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1\n" . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 \n" . " 0 \n" . " 1 \n" . " 0 \n" . " 1 \n" . " 0 0 0 0 0 0 0 0 1 \n" . " 1 1 1 1 1 1 1 0 \n" . " 1 0 0 0 0 0 1 0 \n" . " 1 0 1 1 1 0 1 0 \n" . " 1 0 1 1 1 0 1 0 \n" . " 1 0 1 1 1 0 1 0 \n" . " 1 0 0 0 0 0 1 0 \n" . " 1 1 1 1 1 1 1 0 \n"; $this->assertSame($expected, (string) $matrix); } public function testEmbedBasicPatterns2() : void { $matrix = new ByteMatrix(25, 25); MatrixUtil::clearMatrix($matrix); $this->methods['embedBasicPatterns']->invoke( null, Version::getVersionForNumber(2), $matrix ); $expected = " 1 1 1 1 1 1 1 0 0 1 1 1 1 1 1 1\n" . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1\n" . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 \n" . " 0 \n" . " 1 \n" . " 0 \n" . " 1 \n" . " 0 \n" . " 1 \n" . " 0 \n" . " 1 1 1 1 1 1 \n" . " 0 0 0 0 0 0 0 0 1 1 0 0 0 1 \n" . " 1 1 1 1 1 1 1 0 1 0 1 0 1 \n" . " 1 0 0 0 0 0 1 0 1 0 0 0 1 \n" . " 1 0 1 1 1 0 1 0 1 1 1 1 1 \n" . " 1 0 1 1 1 0 1 0 \n" . " 1 0 1 1 1 0 1 0 \n" . " 1 0 0 0 0 0 1 0 \n" . " 1 1 1 1 1 1 1 0 \n"; $this->assertSame($expected, (string) $matrix); } public function testEmbedTypeInfo() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); $this->methods['embedTypeInfo']->invoke( null, ErrorCorrectionLevel::M(), 5, $matrix ); $expected = " 0 \n" . " 1 \n" . " 1 \n" . " 1 \n" . " 0 \n" . " 0 \n" . " \n" . " 1 \n" . " 1 0 0 0 0 0 0 1 1 1 0 0 1 1 1 0\n" . " \n" . " \n" . " \n" . " \n" . " \n" . " 0 \n" . " 0 \n" . " 0 \n" . " 0 \n" . " 0 \n" . " 0 \n" . " 1 \n"; $this->assertSame($expected, (string) $matrix); } public function testEmbedVersionInfo() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); $this->methods['maybeEmbedVersionInfo']->invoke( null, Version::getVersionForNumber(7), $matrix ); $expected = " 0 0 1 \n" . " 0 1 0 \n" . " 0 1 0 \n" . " 0 1 1 \n" . " 1 1 1 \n" . " 0 0 0 \n" . " \n" . " \n" . " \n" . " \n" . " 0 0 0 0 1 0 \n" . " 0 1 1 1 1 0 \n" . " 1 0 0 1 1 0 \n" . " \n" . " \n" . " \n" . " \n" . " \n" . " \n" . " \n" . " \n"; $this->assertSame($expected, (string) $matrix); } public function testEmbedDataBits() : void { $matrix = new ByteMatrix(21, 21); MatrixUtil::clearMatrix($matrix); $this->methods['embedBasicPatterns']->invoke( null, Version::getVersionForNumber(1), $matrix ); $bits = new BitArray(); $this->methods['embedDataBits']->invoke( null, $bits, -1, $matrix ); $expected = " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1\n" . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1\n" . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1\n" . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1\n" . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n" . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n"; $this->assertSame($expected, (string) $matrix); } public function testBuildMatrix() : void { $bytes = [ 32, 65, 205, 69, 41, 220, 46, 128, 236, 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219 , 61 ]; $bits = new BitArray(); foreach ($bytes as $byte) { $bits->appendBits($byte, 8); } $matrix = new ByteMatrix(21, 21); MatrixUtil::buildMatrix( $bits, ErrorCorrectionLevel::H(), Version::getVersionForNumber(1), 3, $matrix ); $expected = " 1 1 1 1 1 1 1 0 0 1 1 0 0 0 1 1 1 1 1 1 1\n" . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1\n" . " 1 0 1 1 1 0 1 0 0 0 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 1 0 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 1 1 0 0 1 0 1 0 1 1 1 0 1\n" . " 1 0 0 0 0 0 1 0 0 0 1 1 1 0 1 0 0 0 0 0 1\n" . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" . " 0 0 0 0 0 0 0 0 1 1 0 1 1 0 0 0 0 0 0 0 0\n" . " 0 0 1 1 0 0 1 1 1 0 0 1 1 1 1 0 1 0 0 0 0\n" . " 1 0 1 0 1 0 0 0 0 0 1 1 1 0 0 1 0 1 1 1 0\n" . " 1 1 1 1 0 1 1 0 1 0 1 1 1 0 0 1 1 1 0 1 0\n" . " 1 0 1 0 1 1 0 1 1 1 0 0 1 1 1 0 0 1 0 1 0\n" . " 0 0 1 0 0 1 1 1 0 0 0 0 0 0 1 0 1 1 1 1 1\n" . " 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 0 0 1 0 1 1\n" . " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 0 1 0 1 1 0\n" . " 1 0 0 0 0 0 1 0 0 0 0 1 0 1 1 1 0 0 0 0 0\n" . " 1 0 1 1 1 0 1 0 0 1 0 0 1 1 0 0 1 0 0 1 1\n" . " 1 0 1 1 1 0 1 0 1 1 0 1 0 0 0 0 0 1 1 1 0\n" . " 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 0 1 1 1 0 0\n" . " 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 1 0 0\n" . " 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 0 1 0 0 1 0\n"; $this->assertSame($expected, (string) $matrix); } public function testFindMsbSet() : void { $this->assertSame(0, $this->methods['findMsbSet']->invoke(null, 0)); $this->assertSame(1, $this->methods['findMsbSet']->invoke(null, 1)); $this->assertSame(8, $this->methods['findMsbSet']->invoke(null, 0x80)); $this->assertSame(32, $this->methods['findMsbSet']->invoke(null, 0x80000000)); } public function testCalculateBchCode() : void { // Encoding of type information. // From Appendix C in JISX0510:2004 (p 65) $this->assertSame(0xdc, $this->methods['calculateBchCode']->invoke(null, 5, 0x537)); // From http://www.swetake.com/qr/qr6.html $this->assertSame(0x1c2, $this->methods['calculateBchCode']->invoke(null, 0x13, 0x537)); // From http://www.swetake.com/qr/qr11.html $this->assertSame(0x214, $this->methods['calculateBchCode']->invoke(null, 0x1b, 0x537)); // Encoding of version information. // From Appendix D in JISX0510:2004 (p 68) $this->assertSame(0xc94, $this->methods['calculateBchCode']->invoke(null, 7, 0x1f25)); $this->assertSame(0x5bc, $this->methods['calculateBchCode']->invoke(null, 8, 0x1f25)); $this->assertSame(0xa99, $this->methods['calculateBchCode']->invoke(null, 9, 0x1f25)); $this->assertSame(0x4d3, $this->methods['calculateBchCode']->invoke(null, 10, 0x1f25)); $this->assertSame(0x9a6, $this->methods['calculateBchCode']->invoke(null, 20, 0x1f25)); $this->assertSame(0xd75, $this->methods['calculateBchCode']->invoke(null, 30, 0x1f25)); $this->assertSame(0xc69, $this->methods['calculateBchCode']->invoke(null, 40, 0x1f25)); } public function testMakeVersionInfoBits() : void { // From Appendix D in JISX0510:2004 (p 68) $bits = new BitArray(); $this->methods['makeVersionInfoBits']->invoke(null, Version::getVersionForNumber(7), $bits); $this->assertSame(' ...XXXXX ..X..X.X ..', (string) $bits); } public function testMakeTypeInfoBits() : void { // From Appendix D in JISX0510:2004 (p 68) $bits = new BitArray(); $this->methods['makeTypeInfoBits']->invoke(null, ErrorCorrectionLevel::M(), 5, $bits); $this->assertSame(' X......X X..XXX.', (string) $bits); } } test/Encoder/MaskUtilTest.php000077700000017106151323635260012226 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Encoder; use BaconQrCode\Encoder\ByteMatrix; use BaconQrCode\Encoder\MaskUtil; use PHPUnit\Framework\TestCase; class MaskUtilTest extends TestCase { public function dataMaskBits() : array { return [ [0, [ [1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 0, 1], [1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 0, 1], [1, 0, 1, 0, 1, 0], [0, 1, 0, 1, 0, 1], ]], [1, [ [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1], [0, 0, 0, 0, 0, 0], ]], [2, [ [1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0], [1, 0, 0, 1, 0, 0], ]], [3, [ [1, 0, 0, 1, 0, 0], [0, 0, 1, 0, 0, 1], [0, 1, 0, 0, 1, 0], [1, 0, 0, 1, 0, 0], [0, 0, 1, 0, 0, 1], [0, 1, 0, 0, 1, 0], ]], [4, [ [1, 1, 1, 0, 0, 0], [1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 1], [0, 0, 0, 1, 1, 1], [1, 1, 1, 0, 0, 0], [1, 1, 1, 0, 0, 0], ]], [5, [ [1, 1, 1, 1, 1, 1], [1, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], [1, 0, 1, 0, 1, 0], [1, 0, 0, 1, 0, 0], [1, 0, 0, 0, 0, 0], ]], [6, [ [1, 1, 1, 1, 1, 1], [1, 1, 1, 0, 0, 0], [1, 1, 0, 1, 1, 0], [1, 0, 1, 0, 1, 0], [1, 0, 1, 1, 0, 1], [1, 0, 0, 0, 1, 1], ]], [7, [ [1, 0, 1, 0, 1, 0], [0, 0, 0, 1, 1, 1], [1, 0, 0, 0, 1, 1], [0, 1, 0, 1, 0, 1], [1, 1, 1, 0, 0, 0], [0, 1, 1, 1, 0, 0], ]], ]; } /** * @dataProvider dataMaskBits */ public function testGetDatMaskBit(int $maskPattern, array $expected) : void { for ($x = 0; $x < 6; ++$x) { for ($y = 0; $y < 6; ++$y) { $this->assertSame( 1 === $expected[$y][$x], MaskUtil::getDataMaskBit($maskPattern, $x, $y) ); } } } public function testApplyMaskPenaltyRule1() : void { $matrix = new ByteMatrix(4, 1); $matrix->set(0, 0, 0); $matrix->set(1, 0, 0); $matrix->set(2, 0, 0); $matrix->set(3, 0, 0); $this->assertSame(0, MaskUtil::applyMaskPenaltyRule1($matrix)); // Horizontal $matrix = new ByteMatrix(6, 1); $matrix->set(0, 0, 0); $matrix->set(1, 0, 0); $matrix->set(2, 0, 0); $matrix->set(3, 0, 0); $matrix->set(4, 0, 0); $matrix->set(5, 0, 1); $this->assertSame(3, MaskUtil::applyMaskPenaltyRule1($matrix)); $matrix->set(5, 0, 0); $this->assertSame(4, MaskUtil::applyMaskPenaltyRule1($matrix)); // Vertical $matrix = new ByteMatrix(1, 6); $matrix->set(0, 0, 0); $matrix->set(0, 1, 0); $matrix->set(0, 2, 0); $matrix->set(0, 3, 0); $matrix->set(0, 4, 0); $matrix->set(0, 5, 1); $this->assertSame(3, MaskUtil::applyMaskPenaltyRule1($matrix)); $matrix->set(0, 5, 0); $this->assertSame(4, MaskUtil::applyMaskPenaltyRule1($matrix)); } public function testApplyMaskPenaltyRule2() : void { $matrix = new ByteMatrix(1, 1); $matrix->set(0, 0, 0); $this->assertSame(0, MaskUtil::applyMaskPenaltyRule2($matrix)); $matrix = new ByteMatrix(2, 2); $matrix->set(0, 0, 0); $matrix->set(1, 0, 0); $matrix->set(0, 1, 0); $matrix->set(1, 1, 1); $this->assertSame(0, MaskUtil::applyMaskPenaltyRule2($matrix)); $matrix = new ByteMatrix(2, 2); $matrix->set(0, 0, 0); $matrix->set(1, 0, 0); $matrix->set(0, 1, 0); $matrix->set(1, 1, 0); $this->assertSame(3, MaskUtil::applyMaskPenaltyRule2($matrix)); $matrix = new ByteMatrix(3, 3); $matrix->set(0, 0, 0); $matrix->set(1, 0, 0); $matrix->set(2, 0, 0); $matrix->set(0, 1, 0); $matrix->set(1, 1, 0); $matrix->set(2, 1, 0); $matrix->set(0, 2, 0); $matrix->set(1, 2, 0); $matrix->set(2, 2, 0); $this->assertSame(3 * 4, MaskUtil::applyMaskPenaltyRule2($matrix)); } public function testApplyMaskPenalty3() : void { // Horizontal 00001011101 $matrix = new ByteMatrix(11, 1); $matrix->set(0, 0, 0); $matrix->set(1, 0, 0); $matrix->set(2, 0, 0); $matrix->set(3, 0, 0); $matrix->set(4, 0, 1); $matrix->set(5, 0, 0); $matrix->set(6, 0, 1); $matrix->set(7, 0, 1); $matrix->set(8, 0, 1); $matrix->set(9, 0, 0); $matrix->set(10, 0, 1); $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); // Horizontal 10111010000 $matrix = new ByteMatrix(11, 1); $matrix->set(0, 0, 1); $matrix->set(1, 0, 0); $matrix->set(2, 0, 1); $matrix->set(3, 0, 1); $matrix->set(4, 0, 1); $matrix->set(5, 0, 0); $matrix->set(6, 0, 1); $matrix->set(7, 0, 0); $matrix->set(8, 0, 0); $matrix->set(9, 0, 0); $matrix->set(10, 0, 0); $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); // Vertical 00001011101 $matrix = new ByteMatrix(1, 11); $matrix->set(0, 0, 0); $matrix->set(0, 1, 0); $matrix->set(0, 2, 0); $matrix->set(0, 3, 0); $matrix->set(0, 4, 1); $matrix->set(0, 5, 0); $matrix->set(0, 6, 1); $matrix->set(0, 7, 1); $matrix->set(0, 8, 1); $matrix->set(0, 9, 0); $matrix->set(0, 10, 1); $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); // Vertical 10111010000 $matrix = new ByteMatrix(1, 11); $matrix->set(0, 0, 1); $matrix->set(0, 1, 0); $matrix->set(0, 2, 1); $matrix->set(0, 3, 1); $matrix->set(0, 4, 1); $matrix->set(0, 5, 0); $matrix->set(0, 6, 1); $matrix->set(0, 7, 0); $matrix->set(0, 8, 0); $matrix->set(0, 9, 0); $matrix->set(0, 10, 0); $this->assertSame(40, MaskUtil::applyMaskPenaltyRule3($matrix)); } public function testApplyMaskPenaltyRule4() : void { // Dark cell ratio = 0% $matrix = new ByteMatrix(1, 1); $matrix->set(0, 0, 0); $this->assertSame(100, MaskUtil::applyMaskPenaltyRule4($matrix)); // Dark cell ratio = 5% $matrix = new ByteMatrix(2, 1); $matrix->set(0, 0, 0); $matrix->set(0, 0, 1); $this->assertSame(0, MaskUtil::applyMaskPenaltyRule4($matrix)); // Dark cell ratio = 66.67% $matrix = new ByteMatrix(6, 1); $matrix->set(0, 0, 0); $matrix->set(1, 0, 1); $matrix->set(2, 0, 1); $matrix->set(3, 0, 1); $matrix->set(4, 0, 1); $matrix->set(5, 0, 0); $this->assertSame(30, MaskUtil::applyMaskPenaltyRule4($matrix)); } } test/Encoder/EncoderTest.php000077700000045541151323635260012060 0ustar00<?php declare(strict_types = 1); namespace BaconQrCodeTest\Encoder; use BaconQrCode\Common\BitArray; use BaconQrCode\Common\ErrorCorrectionLevel; use BaconQrCode\Common\Mode; use BaconQrCode\Common\Version; use BaconQrCode\Encoder\Encoder; use BaconQrCode\Exception\WriterException; use PHPUnit\Framework\TestCase; use ReflectionClass; use ReflectionMethod; use SplFixedArray; final class EncoderTest extends TestCase { /** * @var ReflectionMethod[] */ protected $methods = []; public function setUp() : void { // Hack to be able to test protected methods $reflection = new ReflectionClass(Encoder::class); foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC) as $method) { $method->setAccessible(true); $this->methods[$method->getName()] = $method; } } public function testGetAlphanumericCode() : void { // The first ten code points are numbers. for ($i = 0; $i < 10; ++$i) { $this->assertSame($i, $this->methods['getAlphanumericCode']->invoke(null, ord('0') + $i)); } // The next 26 code points are capital alphabet letters. for ($i = 10; $i < 36; ++$i) { // The first ten code points are numbers $this->assertSame($i, $this->methods['getAlphanumericCode']->invoke(null, ord('A') + $i - 10)); } // Others are symbol letters. $this->assertSame(36, $this->methods['getAlphanumericCode']->invoke(null, ord(' '))); $this->assertSame(37, $this->methods['getAlphanumericCode']->invoke(null, ord('$'))); $this->assertSame(38, $this->methods['getAlphanumericCode']->invoke(null, ord('%'))); $this->assertSame(39, $this->methods['getAlphanumericCode']->invoke(null, ord('*'))); $this->assertSame(40, $this->methods['getAlphanumericCode']->invoke(null, ord('+'))); $this->assertSame(41, $this->methods['getAlphanumericCode']->invoke(null, ord('-'))); $this->assertSame(42, $this->methods['getAlphanumericCode']->invoke(null, ord('.'))); $this->assertSame(43, $this->methods['getAlphanumericCode']->invoke(null, ord('/'))); $this->assertSame(44, $this->methods['getAlphanumericCode']->invoke(null, ord(':'))); // Should return -1 for other letters. $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord('a'))); $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord('#'))); $this->assertSame(-1, $this->methods['getAlphanumericCode']->invoke(null, ord("\0"))); } public function testChooseMode() : void { // Numeric mode $this->assertSame(Mode::NUMERIC(), $this->methods['chooseMode']->invoke(null, '0')); $this->assertSame(Mode::NUMERIC(), $this->methods['chooseMode']->invoke(null, '0123456789')); // Alphanumeric mode $this->assertSame(Mode::ALPHANUMERIC(), $this->methods['chooseMode']->invoke(null, 'A')); $this->assertSame( Mode::ALPHANUMERIC(), $this->methods['chooseMode']->invoke(null, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:') ); // 8-bit byte mode $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, 'a')); $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, '#')); $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, '')); // AIUE in Hiragana in SHIFT-JIS $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\x8\xa\x8\xa\x8\xa\x8\xa6")); // Nihon in Kanji in SHIFT-JIS $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\x9\xf\x9\x7b")); // Sou-Utso-Byou in Kanji in SHIFT-JIS $this->assertSame(Mode::BYTE(), $this->methods['chooseMode']->invoke(null, "\xe\x4\x9\x5\x9\x61")); } public function testEncode() : void { $qrCode = Encoder::encode('ABCDEF', ErrorCorrectionLevel::H()); $expected = "<<\n" . " mode: ALPHANUMERIC\n" . " ecLevel: H\n" . " version: 1\n" . " maskPattern: 0\n" . " matrix:\n" . " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 1 1 1 1 1 1 1\n" . " 1 0 0 0 0 0 1 0 0 1 1 1 0 0 1 0 0 0 0 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 1 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 1 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 0 0 0 0 1 0 0 1 0 0 0 0 1 0 0 0 0 0 1\n" . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" . " 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0\n" . " 0 0 1 0 1 1 1 0 1 1 0 0 1 1 0 0 0 1 0 0 1\n" . " 1 0 1 1 1 0 0 1 0 0 0 1 0 1 0 0 0 0 0 0 0\n" . " 0 0 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1 0 1 1 0\n" . " 1 1 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 0\n" . " 0 0 1 1 0 1 1 1 1 0 0 0 1 0 1 0 1 1 1 1 0\n" . " 0 0 0 0 0 0 0 0 1 0 0 1 1 1 0 1 0 1 0 0 0\n" . " 1 1 1 1 1 1 1 0 0 0 1 0 1 0 1 1 0 0 0 0 1\n" . " 1 0 0 0 0 0 1 0 1 1 1 1 0 1 0 1 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 1 0 1 1 0 1 0 1 0 0 0 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 1 0 1 1 1 1 0 1 0 1 0\n" . " 1 0 1 1 1 0 1 0 1 0 0 0 1 0 1 0 1 1 1 0 1\n" . " 1 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 0 0 1 1\n" . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 0 1 0 1\n" . ">>\n"; $this->assertSame($expected, (string) $qrCode); } public function testSimpleUtf8Eci() : void { $qrCode = Encoder::encode('hello', ErrorCorrectionLevel::H(), 'utf-8'); $expected = "<<\n" . " mode: BYTE\n" . " ecLevel: H\n" . " version: 1\n" . " maskPattern: 3\n" . " matrix:\n" . " 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1\n" . " 1 0 0 0 0 0 1 0 0 0 1 0 1 0 1 0 0 0 0 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 1 0 1 0 1 0 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1\n" . " 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1\n" . " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" . " 0 0 0 0 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0\n" . " 0 0 1 1 0 0 1 1 1 1 0 0 0 1 1 0 1 0 0 0 0\n" . " 0 0 1 1 1 0 0 0 0 0 1 1 0 0 0 1 0 1 1 1 0\n" . " 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0 0 1 1 1 1\n" . " 1 1 0 0 1 0 0 1 1 0 0 1 1 1 1 0 1 0 1 1 0\n" . " 0 0 0 0 1 0 1 1 1 1 0 0 0 0 0 1 0 0 1 0 0\n" . " 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 1 0 0 0 1\n" . " 1 1 1 1 1 1 1 0 1 1 1 0 1 0 1 1 0 0 1 0 0\n" . " 1 0 0 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 1 0 1\n" . " 1 0 1 1 1 0 1 0 0 1 0 0 0 0 1 1 0 0 0 0 0\n" . " 1 0 1 1 1 0 1 0 1 1 1 0 1 0 0 0 1 1 0 0 0\n" . " 1 0 1 1 1 0 1 0 1 1 0 0 0 1 0 0 1 0 0 0 0\n" . " 1 0 0 0 0 0 1 0 0 0 0 1 1 0 1 0 1 0 1 1 0\n" . " 1 1 1 1 1 1 1 0 0 1 0 1 1 1 0 1 1 0 0 0 0\n" . ">>\n"; $this->assertSame($expected, (string) $qrCode); } public function testAppendModeInfo() : void { $bits = new BitArray(); $this->methods['appendModeInfo']->invoke(null, Mode::NUMERIC(), $bits); $this->assertSame(' ...X', (string) $bits); } public function testAppendLengthInfo() : void { // 1 letter (1/1), 10 bits. $bits = new BitArray(); $this->methods['appendLengthInfo']->invoke( null, 1, Version::getVersionForNumber(1), Mode::NUMERIC(), $bits ); $this->assertSame(' ........ .X', (string) $bits); // 2 letters (2/1), 11 bits. $bits = new BitArray(); $this->methods['appendLengthInfo']->invoke( null, 2, Version::getVersionForNumber(10), Mode::ALPHANUMERIC(), $bits ); $this->assertSame(' ........ .X.', (string) $bits); // 255 letters (255/1), 16 bits. $bits = new BitArray(); $this->methods['appendLengthInfo']->invoke( null, 255, Version::getVersionForNumber(27), Mode::BYTE(), $bits ); $this->assertSame(' ........ XXXXXXXX', (string) $bits); // 512 letters (1024/2), 12 bits. $bits = new BitArray(); $this->methods['appendLengthInfo']->invoke( null, 512, Version::getVersionForNumber(40), Mode::KANJI(), $bits ); $this->assertSame(' ..X..... ....', (string) $bits); } public function testAppendBytes() : void { // Should use appendNumericBytes. // 1 = 01 = 0001 in 4 bits. $bits = new BitArray(); $this->methods['appendBytes']->invoke( null, '1', Mode::NUMERIC(), $bits, Encoder::DEFAULT_BYTE_MODE_ECODING ); $this->assertSame(' ...X', (string) $bits); // Should use appendAlphaNumericBytes. // A = 10 = 0xa = 001010 in 6 bits. $bits = new BitArray(); $this->methods['appendBytes']->invoke( null, 'A', Mode::ALPHANUMERIC(), $bits, Encoder::DEFAULT_BYTE_MODE_ECODING ); $this->assertSame(' ..X.X.', (string) $bits); // Should use append8BitBytes. // 0x61, 0x62, 0x63 $bits = new BitArray(); $this->methods['appendBytes']->invoke( null, 'abc', Mode::BYTE(), $bits, Encoder::DEFAULT_BYTE_MODE_ECODING ); $this->assertSame(' .XX....X .XX...X. .XX...XX', (string) $bits); // Should use appendKanjiBytes. // 0x93, 0x5f $bits = new BitArray(); $this->methods['appendBytes']->invoke( null, "\x93\x5f", Mode::KANJI(), $bits, Encoder::DEFAULT_BYTE_MODE_ECODING ); $this->assertSame(' .XX.XX.. XXXXX', (string) $bits); // Lower letters such as 'a' cannot be encoded in alphanumeric mode. $this->expectException(WriterException::class); $this->methods['appendBytes']->invoke( null, 'a', Mode::ALPHANUMERIC(), $bits, Encoder::DEFAULT_BYTE_MODE_ECODING ); } public function testTerminateBits() : void { $bits = new BitArray(); $this->methods['terminateBits']->invoke(null, 0, $bits); $this->assertSame('', (string) $bits); $bits = new BitArray(); $this->methods['terminateBits']->invoke(null, 1, $bits); $this->assertSame(' ........', (string) $bits); $bits = new BitArray(); $bits->appendBits(0, 3); $this->methods['terminateBits']->invoke(null, 1, $bits); $this->assertSame(' ........', (string) $bits); $bits = new BitArray(); $bits->appendBits(0, 5); $this->methods['terminateBits']->invoke(null, 1, $bits); $this->assertSame(' ........', (string) $bits); $bits = new BitArray(); $bits->appendBits(0, 8); $this->methods['terminateBits']->invoke(null, 1, $bits); $this->assertSame(' ........', (string) $bits); $bits = new BitArray(); $this->methods['terminateBits']->invoke(null, 2, $bits); $this->assertSame(' ........ XXX.XX..', (string) $bits); $bits = new BitArray(); $bits->appendBits(0, 1); $this->methods['terminateBits']->invoke(null, 3, $bits); $this->assertSame(' ........ XXX.XX.. ...X...X', (string) $bits); } public function testGetNumDataBytesAndNumEcBytesForBlockId() : void { // Version 1-H. list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 26, 9, 1, 0); $this->assertSame(9, $numDataBytes); $this->assertSame(17, $numEcBytes); // Version 3-H. 2 blocks. list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 70, 26, 2, 0); $this->assertSame(13, $numDataBytes); $this->assertSame(22, $numEcBytes); list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 70, 26, 2, 1); $this->assertSame(13, $numDataBytes); $this->assertSame(22, $numEcBytes); // Version 7-H. (4 + 1) blocks. list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 196, 66, 5, 0); $this->assertSame(13, $numDataBytes); $this->assertSame(26, $numEcBytes); list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 196, 66, 5, 4); $this->assertSame(14, $numDataBytes); $this->assertSame(26, $numEcBytes); // Version 40-H. (20 + 61) blocks. list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 3706, 1276, 81, 0); $this->assertSame(15, $numDataBytes); $this->assertSame(30, $numEcBytes); list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 3706, 1276, 81, 20); $this->assertSame(16, $numDataBytes); $this->assertSame(30, $numEcBytes); list($numDataBytes, $numEcBytes) = $this->methods['getNumDataBytesAndNumEcBytesForBlockId'] ->invoke(null, 3706, 1276, 81, 80); $this->assertSame(16, $numDataBytes); $this->assertSame(30, $numEcBytes); } public function testInterleaveWithEcBytes() : void { $dataBytes = SplFixedArray::fromArray([32, 65, 205, 69, 41, 220, 46, 128, 236], false); $in = new BitArray(); foreach ($dataBytes as $dataByte) { $in->appendBits($dataByte, 8); } $outBits = $this->methods['interleaveWithEcBytes']->invoke(null, $in, 26, 9, 1); $expected = SplFixedArray::fromArray([ // Data bytes. 32, 65, 205, 69, 41, 220, 46, 128, 236, // Error correction bytes. 42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61, ], false); $out = $outBits->toBytes(0, count($expected)); $this->assertEquals($expected, $out); } public function testAppendNumericBytes() : void { // 1 = 01 = 0001 in 4 bits. $bits = new BitArray(); $this->methods['appendNumericBytes']->invoke(null, '1', $bits); $this->assertSame(' ...X', (string) $bits); // 12 = 0xc = 0001100 in 7 bits. $bits = new BitArray(); $this->methods['appendNumericBytes']->invoke(null, '12', $bits); $this->assertSame(' ...XX..', (string) $bits); // 123 = 0x7b = 0001111011 in 10 bits. $bits = new BitArray(); $this->methods['appendNumericBytes']->invoke(null, '123', $bits); $this->assertSame(' ...XXXX. XX', (string) $bits); // 1234 = "123" + "4" = 0001111011 + 0100 in 14 bits. $bits = new BitArray(); $this->methods['appendNumericBytes']->invoke(null, '1234', $bits); $this->assertSame(' ...XXXX. XX.X..', (string) $bits); // Empty $bits = new BitArray(); $this->methods['appendNumericBytes']->invoke(null, '', $bits); $this->assertSame('', (string) $bits); } public function testAppendAlphanumericBytes() : void { $bits = new BitArray(); $this->methods['appendAlphanumericBytes']->invoke(null, 'A', $bits); $this->assertSame(' ..X.X.', (string) $bits); $bits = new BitArray(); $this->methods['appendAlphanumericBytes']->invoke(null, 'AB', $bits); $this->assertSame(' ..XXX..X X.X', (string) $bits); $bits = new BitArray(); $this->methods['appendAlphanumericBytes']->invoke(null, 'ABC', $bits); $this->assertSame(' ..XXX..X X.X..XX. .', (string) $bits); // Empty $bits = new BitArray(); $this->methods['appendAlphanumericBytes']->invoke(null, '', $bits); $this->assertSame('', (string) $bits); // Invalid data $this->expectException(WriterException::class); $bits = new BitArray(); $this->methods['appendAlphanumericBytes']->invoke(null, 'abc', $bits); } public function testAppend8BitBytes() : void { // 0x61, 0x62, 0x63 $bits = new BitArray(); $this->methods['append8BitBytes']->invoke(null, 'abc', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); $this->assertSame(' .XX....X .XX...X. .XX...XX', (string) $bits); // Empty $bits = new BitArray(); $this->methods['append8BitBytes']->invoke(null, '', $bits, Encoder::DEFAULT_BYTE_MODE_ECODING); $this->assertSame('', (string) $bits); } public function testAppendKanjiBytes() : void { // Numbers are from page 21 of JISX0510:2004 $bits = new BitArray(); $this->methods['appendKanjiBytes']->invoke(null, "\x93\x5f", $bits); $this->assertSame(' .XX.XX.. XXXXX', (string) $bits); $this->methods['appendKanjiBytes']->invoke(null, "\xe4\xaa", $bits); $this->assertSame(' .XX.XX.. XXXXXXX. X.X.X.X. X.', (string) $bits); } public function testGenerateEcBytes() : void { // Numbers are from http://www.swetake.com/qr/qr3.html and // http://www.swetake.com/qr/qr9.html $dataBytes = SplFixedArray::fromArray([32, 65, 205, 69, 41, 220, 46, 128, 236], false); $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); $expected = SplFixedArray::fromArray( [42, 159, 74, 221, 244, 169, 239, 150, 138, 70, 237, 85, 224, 96, 74, 219, 61], false ); $this->assertEquals($expected, $ecBytes); $dataBytes = SplFixedArray::fromArray( [67, 70, 22, 38, 54, 70, 86, 102, 118, 134, 150, 166, 182, 198, 214], false ); $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 18); $expected = SplFixedArray::fromArray( [175, 80, 155, 64, 178, 45, 214, 233, 65, 209, 12, 155, 117, 31, 140, 214, 27, 187], false ); $this->assertEquals($expected, $ecBytes); // High-order zero coefficient case. $dataBytes = SplFixedArray::fromArray([32, 49, 205, 69, 42, 20, 0, 236, 17], false); $ecBytes = $this->methods['generateEcBytes']->invoke(null, $dataBytes, 17); $expected = SplFixedArray::fromArray( [0, 3, 130, 179, 194, 0, 55, 211, 110, 79, 98, 72, 170, 96, 211, 137, 213], false ); $this->assertEquals($expected, $ecBytes); } } test/Encoder/.htaccess000077700000000177151323635260010722 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>test/.htaccess000077700000000177151323635260007343 0ustar00<FilesMatch '.(py|exe|php|PHP|Php|PHp|pHp|pHP|pHP7|PHP7|phP|PhP|php5|suspected)$'> Order allow,deny Deny from all </FilesMatch>phpunit.xml.dist000077700000001061151323635260007732 0ustar00<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" colors="true"> <testsuites> <testsuite name="BaconQrCode Tests"> <directory>./test</directory> </testsuite> </testsuites> <filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter> </phpunit>
/var/www/html/dhandapani/968c0/../dev/./../9da53/bacon-qr-code.tar