/** * Encodes "content" with the error correction level "ecLevel". * * @param string $content * @param ErrorCorrectionLevel $ecLevel * @param ? $hints * @return QrCode */ public static function encode($content, ErrorCorrectionLevel $ecLevel, $encoding = self::DEFAULT_BYTE_MODE_ECODING) { // 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); //debug($mode->get()); // 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->get() === Mode::BYTE && $encoding !== self::DEFAULT_BYTE_MODE_ECODING) { $eci = CharacterSetEci::getCharacterSetEciByName($encoding); if ($eci !== null) { self::appendEci($eci, $headerBits); } } // (With ECI in place,) Write the mode marker self::appendModeInfo($mode, $headerBits); // Collect data within the main segment, separately, to count its size // if needed. Don't add it to main payload yet. $dataBits = new BitArray(); self::appendBytes($content, $mode, $dataBits, $encoding); // Hard part: need to know version to know how many bits length takes. // But need to know how many bits it takes to know version. First we // take a guess at version by assuming version will be the minimum, 1: $provisionalBitsNeeded = $headerBits->getSize() + $mode->getCharacterCountBits(Version::getVersionForNumber(1)) + $dataBits->getSize(); $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel); // Use that guess to calculate the right version. I am still not sure // this works in 100% of cases. $bitsNeeded = $headerBits->getSize() + $mode->getCharacterCountBits($provisionalVersion) + $dataBits->getSize(); $version = self::chooseVersion($bitsNeeded, $ecLevel); $headerAndDataBits = new BitArray(); $headerAndDataBits->appendBitArray($headerBits); // Find "length" of main segment and write it. $numLetters = $mode->get() === Mode::BYTE ? $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()); $qrCode = new QrCode(); $qrCode->setErrorCorrectionLevel($ecLevel); $qrCode->setMode($mode); $qrCode->setVersion($version); // Choose the mask pattern and set to "qrCode". $dimension = $version->getDimensionForVersion(); $matrix = new ByteMatrix($dimension, $dimension); $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix); $qrCode->setMaskPattern($maskPattern); // Build the matrix and set it to "qrCode". MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix); $qrCode->setMatrix($matrix); return $qrCode; }
/** * render(): defined by RendererInterface. * * @see RendererInterface::render() * @param QrCode $qrCode * @return string */ public function render(QrCode $qrCode) { $input = $qrCode->getMatrix(); $inputWidth = $input->getWidth(); $inputHeight = $input->getHeight(); $qrWidth = $inputWidth + ($this->getMargin() << 1); $qrHeight = $inputHeight + ($this->getMargin() << 1); $outputWidth = max($this->getWidth(), $qrWidth); $outputHeight = max($this->getHeight(), $qrHeight); $multiple = (int) min($outputWidth / $qrWidth, $outputHeight / $qrHeight); if ($this->shouldRoundDimensions()) { $outputWidth -= $outputWidth % $multiple; $outputHeight -= $outputHeight % $multiple; } // Padding includes both the quiet zone and the extra white pixels to // accommodate the requested dimensions. For example, if input is 25x25 // the QR will be 33x33 including the quiet zone. If the requested size // is 200x160, the multiple will be 4, for a QR of 132x132. These will // handle all the padding from 100x100 (the actual QR) up to 200x160. $leftPadding = (int) (($outputWidth - $inputWidth * $multiple) / 2); $topPadding = (int) (($outputHeight - $inputHeight * $multiple) / 2); // Store calculated parameters $this->finalWidth = $outputWidth; $this->finalHeight = $outputHeight; $this->blockSize = $multiple; $this->init(); $this->addColor('background', $this->getBackgroundColor()); $this->addColor('foreground', $this->getForegroundColor()); $this->drawBackground('background'); foreach ($this->decorators as $decorator) { $decorator->preProcess($qrCode, $this, $outputWidth, $outputHeight, $leftPadding, $topPadding, $multiple); } for ($inputY = 0, $outputY = $topPadding; $inputY < $inputHeight; $inputY++, $outputY += $multiple) { for ($inputX = 0, $outputX = $leftPadding; $inputX < $inputWidth; $inputX++, $outputX += $multiple) { if ($input->get($inputX, $inputY) === 1) { $this->drawBlock($outputX, $outputY, 'foreground'); } } } foreach ($this->decorators as $decorator) { $decorator->postProcess($qrCode, $this, $outputWidth, $outputHeight, $leftPadding, $topPadding, $multiple); } return $this->getByteStream(); }
/** * postProcess(): defined by DecoratorInterface. * * @see DecoratorInterface::postProcess() * * @param QrCode $qrCode * @param RendererInterface $renderer * @param integer $outputWidth * @param integer $outputHeight * @param integer $leftPadding * @param integer $topPadding * @param integer $multiple * @return void */ public function postProcess(QrCode $qrCode, RendererInterface $renderer, $outputWidth, $outputHeight, $leftPadding, $topPadding, $multiple) { $matrix = $qrCode->getMatrix(); $positions = array(array(0, 0), array($matrix->getWidth() - 7, 0), array(0, $matrix->getHeight() - 7)); $renderer->addColor('finder-outer', $this->getOuterColor()); $renderer->addColor('finder-inner', $this->getInnerColor()); foreach (self::$outerPositionDetectionPattern as $y => $row) { foreach ($row as $x => $isOuterSet) { $isInnerSet = self::$innerPositionDetectionPattern[$y][$x]; if ($isOuterSet) { foreach ($positions as $position) { $renderer->drawBlock($leftPadding + $x * $multiple + $position[0] * $multiple, $topPadding + $y * $multiple + $position[1] * $multiple, 'finder-outer'); } } if ($isInnerSet) { foreach ($positions as $position) { $renderer->drawBlock($leftPadding + $x * $multiple + $position[0] * $multiple, $topPadding + $y * $multiple + $position[1] * $multiple, 'finder-inner'); } } } } }
/** * render(): defined by RendererInterface. * * @see RendererInterface::render() * @param QrCode $qrCode * @return string */ public function render(QrCode $qrCode) { $result = ''; $matrix = $qrCode->getMatrix(); $width = $matrix->getWidth(); // Top margin for ($x = 0; $x < $this->margin; $x++) { $result .= str_repeat($this->emptyBlock, $width + 2 * $this->margin) . "\n"; } // Body $array = $matrix->getArray(); foreach ($array as $row) { $result .= str_repeat($this->emptyBlock, $this->margin); // left margin foreach ($row as $byte) { $result .= $byte ? $this->fullBlock : $this->emptyBlock; } $result .= str_repeat($this->emptyBlock, $this->margin); // right margin $result .= "\n"; } // Bottom margin for ($x = 0; $x < $this->margin; $x++) { $result .= str_repeat($this->emptyBlock, $width + 2 * $this->margin) . "\n"; } return $result; }