| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- <?php
- /**
- * Class QRCode
- *
- * @filesource QRCode.php
- * @created 26.11.2015
- * @package chillerlan\QRCode
- * @author Smiley <smiley@chillerlan.net>
- * @copyright 2015 Smiley
- * @license MIT
- */
- namespace chillerlan\QRCode;
- use chillerlan\QRCode\Data\{
- AlphaNum, Byte, Kanji, MaskPatternTester, Number, QRCodeDataException, QRDataInterface, QRMatrix
- };
- use chillerlan\QRCode\Output\{
- QRCodeOutputException, QRImage, QRMarkup, QROutputInterface, QRString
- };
- use chillerlan\Traits\ClassLoader;
- /**
- * Turns a text string into a Model 2 QR Code
- *
- * @link https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
- * @link http://www.qrcode.com/en/codes/model12.html
- * @link http://www.thonky.com/qr-code-tutorial/
- */
- class QRCode{
- use ClassLoader;
- /**
- * API constants
- */
- const OUTPUT_MARKUP_HTML = 'html';
- const OUTPUT_MARKUP_SVG = 'svg';
- # const OUTPUT_MARKUP_EPS = 'eps';
- # const OUTPUT_MARKUP_XML = 'xml'; // anyone?
- const OUTPUT_IMAGE_PNG = 'png';
- const OUTPUT_IMAGE_JPG = 'jpg';
- const OUTPUT_IMAGE_GIF = 'gif';
- const OUTPUT_STRING_JSON = 'json';
- const OUTPUT_STRING_TEXT = 'text';
- const OUTPUT_CUSTOM = 'custom';
- const VERSION_AUTO = -1;
- const MASK_PATTERN_AUTO = -1;
- const ECC_L = 0b01; // 7%.
- const ECC_M = 0b00; // 15%.
- const ECC_Q = 0b11; // 25%.
- const ECC_H = 0b10; // 30%.
- const DATA_NUMBER = 0b0001;
- const DATA_ALPHANUM = 0b0010;
- const DATA_BYTE = 0b0100;
- const DATA_KANJI = 0b1000;
- const ECC_MODES = [
- self::ECC_L => 0,
- self::ECC_M => 1,
- self::ECC_Q => 2,
- self::ECC_H => 3,
- ];
- const DATA_MODES = [
- self::DATA_NUMBER => 0,
- self::DATA_ALPHANUM => 1,
- self::DATA_BYTE => 2,
- self::DATA_KANJI => 3,
- ];
- const OUTPUT_MODES = [
- QRMarkup::class => [
- self::OUTPUT_MARKUP_SVG,
- self::OUTPUT_MARKUP_HTML,
- # self::OUTPUT_MARKUP_EPS,
- ],
- QRImage::class => [
- self::OUTPUT_IMAGE_PNG,
- self::OUTPUT_IMAGE_GIF,
- self::OUTPUT_IMAGE_JPG,
- ],
- QRString::class => [
- self::OUTPUT_STRING_JSON,
- self::OUTPUT_STRING_TEXT,
- ]
- ];
- /**
- * @var \chillerlan\QRCode\QROptions
- */
- protected $options;
- /**
- * @var \chillerlan\QRCode\Data\QRDataInterface
- */
- protected $dataInterface;
- /**
- * QRCode constructor.
- *
- * @param \chillerlan\QRCode\QROptions|null $options
- */
- public function __construct(QROptions $options = null){
- mb_internal_encoding('UTF-8');
- $this->setOptions($options ?? new QROptions);
- }
- /**
- * Sets the options, called internally by the constructor
- *
- * @param \chillerlan\QRCode\QROptions $options
- *
- * @return \chillerlan\QRCode\QRCode
- * @throws \chillerlan\QRCode\QRCodeException
- */
- public function setOptions(QROptions $options):QRCode{
- if(!array_key_exists($options->eccLevel, $this::ECC_MODES)){
- throw new QRCodeException('Invalid error correct level: '.$options->eccLevel);
- }
- if(!is_array($options->imageTransparencyBG) || count($options->imageTransparencyBG) < 3){
- $options->imageTransparencyBG = [255, 255, 255];
- }
- $options->version = (int)$options->version;
- // clamp min/max version number
- $options->versionMin = (int)min($options->versionMin, $options->versionMax);
- $options->versionMax = (int)max($options->versionMin, $options->versionMax);
- $this->options = $options;
- return $this;
- }
- /**
- * Renders a QR Code for the given $data and QROptions
- *
- * @param string $data
- *
- * @return mixed
- */
- public function render(string $data){
- return $this->initOutputInterface($data)->dump();
- }
- /**
- * Returns a QRMatrix object for the given $data and current QROptions
- *
- * @param string $data
- *
- * @return \chillerlan\QRCode\Data\QRMatrix
- * @throws \chillerlan\QRCode\Data\QRCodeDataException
- */
- public function getMatrix(string $data):QRMatrix {
- //NOTE: input sanitization should be done outside
- //$data = trim($data);
-
- if(empty($data)){
- throw new QRCodeDataException('QRCode::getMatrix() No data given.');
- }
- $this->dataInterface = $this->initDataInterface($data);
- $maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
- ? $this->getBestMaskPattern()
- : min(7, max(0, (int)$this->options->maskPattern));
- $matrix = $this
- ->dataInterface
- ->initMatrix($maskPattern)
- ;
- if((bool)$this->options->addQuietzone){
- $matrix->setQuietZone($this->options->quietzoneSize);
- }
- return $matrix;
- }
- /**
- * shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
- *
- * @see \chillerlan\QRCode\Data\MaskPatternTester
- *
- * @return int
- */
- protected function getBestMaskPattern():int{
- $penalties = [];
- $tester = new MaskPatternTester;
- for($testPattern = 0; $testPattern < 8; $testPattern++){
- $matrix = $this
- ->dataInterface
- ->initMatrix($testPattern, true);
- $tester->setMatrix($matrix);
- $penalties[$testPattern] = $tester->testPattern();
- }
- return array_search(min($penalties), $penalties, true);
- }
- /**
- * returns a fresh QRDataInterface for the given $data
- *
- * @param string $data
- *
- * @return \chillerlan\QRCode\Data\QRDataInterface
- * @throws \chillerlan\QRCode\Data\QRCodeDataException
- */
- public function initDataInterface(string $data):QRDataInterface{
- $DATA_MODES = [
- Number::class => 'Number',
- AlphaNum::class => 'AlphaNum',
- Kanji::class => 'Kanji',
- Byte::class => 'Byte',
- ];
- foreach($DATA_MODES as $dataInterface => $mode){
- if(call_user_func_array([$this, 'is'.$mode], [$data]) === true){
- return $this->loadClass($dataInterface, QRDataInterface::class, $this->options, $data);
- }
- }
- throw new QRCodeDataException('invalid data type'); // @codeCoverageIgnore
- }
- /**
- * returns a fresh (built-in) QROutputInterface
- *
- * @param string $data
- *
- * @return \chillerlan\QRCode\Output\QROutputInterface
- * @throws \chillerlan\QRCode\Output\QRCodeOutputException
- */
- protected function initOutputInterface(string $data):QROutputInterface{
- if($this->options->outputType === $this::OUTPUT_CUSTOM && $this->options->outputInterface !== null){
- return $this->loadClass($this->options->outputInterface, QROutputInterface::class, $this->options, $this->getMatrix($data));
- }
- foreach($this::OUTPUT_MODES as $outputInterface => $modes){
- if(in_array($this->options->outputType, $modes, true)){
- return $this->loadClass($outputInterface, QROutputInterface::class, $this->options, $this->getMatrix($data));
- }
- }
- throw new QRCodeOutputException('invalid output type');
- }
- /**
- * checks if a string qualifies as numeric
- *
- * @param string $string
- *
- * @return bool
- */
- public function isNumber(string $string):bool {
- $len = strlen($string);
- $map = str_split('0123456789');
- for($i = 0; $i < $len; $i++){
- if(!in_array($string[$i], $map, true)){
- return false;
- }
- }
- return true;
- }
- /**
- * checks if a string qualifies as alphanumeric
- *
- * @param string $string
- *
- * @return bool
- */
- public function isAlphaNum(string $string):bool {
- $len = strlen($string);
- for($i = 0; $i < $len; $i++){
- if(!in_array($string[$i], AlphaNum::CHAR_MAP, true)){
- return false;
- }
- }
- return true;
- }
- /**
- * checks if a string qualifies as Kanji
- *
- * @param string $string
- *
- * @return bool
- */
- public function isKanji(string $string):bool {
- $i = 0;
- $len = strlen($string);
- while($i + 1 < $len){
- $c = ((0xff&ord($string[$i])) << 8)|(0xff&ord($string[$i + 1]));
- if(!($c >= 0x8140 && $c <= 0x9FFC) && !($c >= 0xE040 && $c <= 0xEBBF)){
- return false;
- }
- $i += 2;
- }
- return $i >= $len;
- }
- /**
- * a dummy
- *
- * @param $data
- *
- * @return bool
- */
- protected function isByte(string $data):bool{
- return !empty($data);
- }
- }
|