fjffk简单的咖啡店科开发2方法2 环滁皆山也。其西南诸峰,林壑尤美,望之蔚然而深秀者,琅琊也。山行六七里,渐闻水声潺潺,而泻出于两峰之间者,酿泉也 ??????????????????????????????? ??????????????/????????????????? ............/................... .........................../... ??????????????????????????????? >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>> ÿØÿà JFIF    ÿÛ „  ( %!1!%)+//.383,7(-.+  -%%-////---/-.+/--+------/------/--0+--/-/-----.-----ÿÀ  ¥2" ÿÄ     ÿÄ J    ! 1AQ"aq2‘#BR‚¡ÁÑ3br’¢±Âð$CSƒ²á4c“%DsÓñÿÄ   ÿÄ *  !1AQa‘"2q3±ð#b¡ÿÚ   ? ¼QxJQaÍuò¸Zö Úü8,ÐÚú "SSn<rçù–´âE—^ªBÖ9À\†¸ÔÁT­ÃÛ5 ëd´³Í#Ý;Þ38œî ¶H£M:wÎ3…³…âpÔF&‚FK¸9„â4àGEõªfÿ ‘ñ(ßw­pŽF|È¥ù®häðÍѶ¹‘[ÒinÙW¶ùñY˜Q{›K"išÒ[Ú8žë\F¹@-?v"ÔU”,ìöžkÿ {I‡£šÍ?e ríV ?> ......................................... ............................................................................. <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>/>>>>>>>>>>>>>>>>>> <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< >>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ppbacon-qr-code/src/Renderer/Module/ModuleInterface.php000075500000000753152503640260016532 0ustar00getSimplifiedPoints(); $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; } } bacon-qr-code/src/Renderer/Module/RoundnessModule.php000075500000010574152503640260016614 0ustar00 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; } } bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php000075500000003771152503640260016711 0ustar00 */ private $points = []; /** * @var array|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 */ 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; } } bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php000075500000007151152503640260020417 0ustar00bytes = iterator_to_array($matrix->getBytes()); $this->size = count($this->bytes); $this->width = $matrix->getWidth(); $this->height = $matrix->getHeight(); } /** * @return Traversable */ 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 ); } } bacon-qr-code/src/Renderer/Module/DotsModule.php000075500000003434152503640260015542 0ustar00 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; } } bacon-qr-code/src/Renderer/PlainTextRenderer.php000075500000004216152503640260015634 0ustar00margin = $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; } } bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php000075500000002307152503640260015774 0ustar00move(-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() ; } } bacon-qr-code/src/Renderer/Eye/SquareEye.php000075500000002061152503640260014656 0ustar00move(-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() ; } } bacon-qr-code/src/Renderer/Eye/CompositeEye.php000075500000001366152503640260015367 0ustar00externalEye = $externalEye; $this->internalEye = $internalEye; } public function getExternalPath() : Path { return $this->externalEye->getExternalPath(); } public function getInternalPath() : Path { return $this->internalEye->getInternalPath(); } } bacon-qr-code/src/Renderer/Eye/ModuleEye.php000075500000002253152503640260014646 0ustar00module = $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); } } bacon-qr-code/src/Renderer/Eye/EyeInterface.php000075500000001124152503640260015315 0ustar00imageFormat = $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($width, $height); $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, $width, $height); $this->draw->composite(Imagick::COMPOSITE_COPY, 0, 0, $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())); } } bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php000075500000027273152503640260016171 0ustar00eps = "%!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()); } } bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php000075500000004724152503640260017336 0ustar00xmlWriter = 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', (string) $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', (string) $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() ); } } bacon-qr-code/src/Renderer/Image/TransformationMatrix.php000075500000003771152503640260017457 0ustar00values = [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], ]; } } bacon-qr-code/src/Renderer/RendererStyle/Fill.php000075500000010061152503640260015705 0ustar00backgroundColor = $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; } } bacon-qr-code/src/Renderer/RendererStyle/Gradient.php000075500000001532152503640260016557 0ustar00startColor = $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; } } bacon-qr-code/src/Renderer/RendererStyle/RendererStyle.php000075500000003257152503640260017617 0ustar00margin = $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; } } bacon-qr-code/src/Renderer/RendererStyle/EyeFill.php000075500000003301152503640260016347 0ustar00externalColor = $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; } } bacon-qr-code/src/Renderer/RendererStyle/GradientType.php000075500000001036152503640260017420 0ustar00rendererStyle = $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; } } bacon-qr-code/src/Renderer/RendererInterface.php000075500000000271152503640260015621 0ustar00 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(); } } bacon-qr-code/src/Renderer/Color/Rgb.php000075500000003645152503640260014032 0ustar00 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)); } } bacon-qr-code/src/Renderer/Color/ColorInterface.php000075500000000555152503640260016214 0ustar00 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(); } } bacon-qr-code/src/Renderer/Color/Gray.php000075500000001637152503640260014221 0ustar00 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; } } bacon-qr-code/src/Renderer/Path/Close.php000075500000000770152503640260014177 0ustar00x = $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); } } bacon-qr-code/src/Renderer/Path/Path.php000075500000004644152503640260014032 0ustar00operations[] = 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; } } } bacon-qr-code/src/Renderer/Path/EllipticArc.php000075500000015523152503640260015327 0ustar00xRadius = 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 */ 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, float $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), ]; } } bacon-qr-code/src/Renderer/Path/Line.php000075500000001220152503640260014010 0ustar00x = $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); } } bacon-qr-code/src/Renderer/Path/OperationInterface.php000075500000000342152503640260016706 0ustar00x1 = $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 ); } } bacon-qr-code/src/Common/EcBlocks.php000075500000003212152503640260013377 0ustar00ecCodewordsPerBlock = $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; } } bacon-qr-code/src/Common/BitArray.php000075500000021323152503640260013432 0ustar00 */ 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 */ 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 */ 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; } } bacon-qr-code/src/Common/ReedSolomonCodec.php000075500000035071152503640260015106 0ustar00 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; } } bacon-qr-code/src/Common/BitMatrix.php000075500000017070152503640260013624 0ustar00 */ 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; } } bacon-qr-code/src/Common/Mode.php000075500000004077152503640260012610 0ustar00characterCountBitsForVersions = $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; } } bacon-qr-code/src/Common/BitUtils.php000075500000001612152503640260013453 0ustar00>>" 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; } } bacon-qr-code/src/Common/EcBlock.php000075500000001750152503640260013221 0ustar00count = $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; } } bacon-qr-code/src/Common/Version.php000075500000052560152503640260013351 0ustar00|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 */ 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)) ), ]; } } bacon-qr-code/src/Common/CharacterSetEci.php000075500000012344152503640260014711 0ustar00|null */ private static $valueToEci; /** * @var array|null */ private static $nameToEci; /** * @param int[] $values */ 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; } } bacon-qr-code/src/Common/ErrorCorrectionLevel.php000075500000002475152503640260016035 0ustar00bits = $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; } } bacon-qr-code/src/Common/FormatInformation.php000075500000013242152503640260015354 0ustar00ecLevel = 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 ); } } bacon-qr-code/src/Writer.php000075500000003500152503640260011736 0ustar00renderer = $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, ?Version $forcedVersion = 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, $forcedVersion)); } /** * 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, ?Version $forcedVersion = null ) : void { file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel, $forcedVersion)); } } bacon-qr-code/src/Encoder/Encoder.php000075500000052757152503640260013442 0ustar00 */ 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, ?Version $forcedVersion = null ) : 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); if (null !== $forcedVersion) { // Forced version check if ($version->getVersionNumber() <= $forcedVersion->getVersionNumber()) { // Calculated minimum version is same or equal as forced version $version = $forcedVersion; } else { throw new WriterException( 'Invalid version! Calculated version: ' . $version->getVersionNumber() . ', requested version: ' . $forcedVersion->getVersionNumber() ); } } $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 $dataBytes * @return SplFixedArray */ 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); } } bacon-qr-code/src/Encoder/MatrixUtil.php000075500000040752152503640260014155 0ustar00clear(-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() .')'); } } } bacon-qr-code/src/Encoder/MaskUtil.php000075500000020272152503640260013577 0ustar00getArray(); $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) + (int) ($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; } } bacon-qr-code/src/Encoder/BlockPair.php000075500000002126152503640260013712 0ustar00 */ private $dataBytes; /** * Error correction bytes in the block. * * @var SplFixedArray */ private $errorCorrectionBytes; /** * Creates a new block pair. * * @param SplFixedArray $data * @param SplFixedArray $errorCorrection */ public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection) { $this->dataBytes = $data; $this->errorCorrectionBytes = $errorCorrection; } /** * Gets the data bytes. * * @return SplFixedArray */ public function getDataBytes() : SplFixedArray { return $this->dataBytes; } /** * Gets the error correction bytes. * * @return SplFixedArray */ public function getErrorCorrectionBytes() : SplFixedArray { return $this->errorCorrectionBytes; } } bacon-qr-code/src/Encoder/ByteMatrix.php000075500000005727152503640260014146 0ustar00> */ 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> */ public function getArray() : SplFixedArray { return $this->bytes; } /** * @return Traversable */ 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; } } bacon-qr-code/src/Encoder/QrCode.php000075500000005324152503640260013224 0ustar00mode = $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; } } bacon-qr-code/src/Exception/RuntimeException.php000075500000000235152503640260015724 0ustar00writeFile('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 bacon-qr-code/LICENSE000075500000002434152503640260010174 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.