QRCode.php 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <?php
  2. /**
  3. * Class QRCode
  4. *
  5. * @created 26.11.2015
  6. * @author Smiley <smiley@chillerlan.net>
  7. * @copyright 2015 Smiley
  8. * @license MIT
  9. */
  10. namespace chillerlan\QRCode;
  11. use chillerlan\QRCode\Common\{EccLevel, ECICharset, MaskPattern, Mode};
  12. use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Kanji, Number, QRCodeDataException, QRData, QRDataModeInterface, QRMatrix};
  13. use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, LuminanceSourceInterface};
  14. use chillerlan\QRCode\Output\{QRCodeOutputException, QRFpdf, QRGdImage, QRImagick, QRMarkup, QROutputInterface, QRString};
  15. use chillerlan\Settings\SettingsContainerInterface;
  16. use function class_exists, class_implements, in_array, mb_convert_encoding, mb_detect_encoding;
  17. /**
  18. * Turns a text string into a Model 2 QR Code
  19. *
  20. * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
  21. * @see http://www.qrcode.com/en/codes/model12.html
  22. * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
  23. * @see https://en.wikipedia.org/wiki/QR_code
  24. * @see http://www.thonky.com/qr-code-tutorial/
  25. */
  26. class QRCode{
  27. /** @var int */
  28. public const VERSION_AUTO = -1;
  29. /** @var int */
  30. public const MASK_PATTERN_AUTO = -1;
  31. /**
  32. * @deprecated backward compatibility
  33. * @see \chillerlan\QRCode\Common\EccLevel
  34. */
  35. /** @var int */
  36. public const ECC_L = EccLevel::L;
  37. /** @var int */
  38. public const ECC_M = EccLevel::M;
  39. /** @var int */
  40. public const ECC_Q = EccLevel::Q;
  41. /** @var int */
  42. public const ECC_H = EccLevel::H;
  43. /** @var string */
  44. public const OUTPUT_MARKUP_HTML = 'html';
  45. /** @var string */
  46. public const OUTPUT_MARKUP_SVG = 'svg';
  47. /** @var string */
  48. public const OUTPUT_IMAGE_PNG = 'png';
  49. /** @var string */
  50. public const OUTPUT_IMAGE_JPG = 'jpg';
  51. /** @var string */
  52. public const OUTPUT_IMAGE_GIF = 'gif';
  53. /** @var string */
  54. public const OUTPUT_STRING_JSON = 'json';
  55. /** @var string */
  56. public const OUTPUT_STRING_TEXT = 'text';
  57. /** @var string */
  58. public const OUTPUT_IMAGICK = 'imagick';
  59. /** @var string */
  60. public const OUTPUT_FPDF = 'fpdf';
  61. /** @var string */
  62. public const OUTPUT_CUSTOM = 'custom';
  63. /**
  64. * Map of built-in output modes => modules
  65. *
  66. * @var string[]
  67. */
  68. public const OUTPUT_MODES = [
  69. self::OUTPUT_MARKUP_SVG => QRMarkup::class,
  70. self::OUTPUT_MARKUP_HTML => QRMarkup::class,
  71. self::OUTPUT_IMAGE_PNG => QRGdImage::class,
  72. self::OUTPUT_IMAGE_GIF => QRGdImage::class,
  73. self::OUTPUT_IMAGE_JPG => QRGdImage::class,
  74. self::OUTPUT_STRING_JSON => QRString::class,
  75. self::OUTPUT_STRING_TEXT => QRString::class,
  76. self::OUTPUT_IMAGICK => QRImagick::class,
  77. self::OUTPUT_FPDF => QRFpdf::class,
  78. ];
  79. /**
  80. * The settings container
  81. *
  82. * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
  83. */
  84. protected SettingsContainerInterface $options;
  85. /**
  86. * A collection of one or more data segments of [classname, data] to write
  87. *
  88. * @see \chillerlan\QRCode\Data\QRDataModeInterface
  89. *
  90. * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
  91. */
  92. protected array $dataSegments = [];
  93. /**
  94. * QRCode constructor.
  95. *
  96. * Sets the options instance
  97. */
  98. public function __construct(SettingsContainerInterface $options = null){
  99. $this->options = $options ?? new QROptions;
  100. }
  101. /**
  102. * Renders a QR Code for the given $data and QROptions
  103. *
  104. * @return mixed
  105. */
  106. public function render(string $data = null, string $file = null){
  107. if($data !== null){
  108. /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
  109. foreach(Mode::INTERFACES as $dataInterface){
  110. if($dataInterface::validateString($data)){
  111. $this->addSegment(new $dataInterface($data));
  112. break;
  113. }
  114. }
  115. }
  116. return $this->initOutputInterface()->dump($file);
  117. }
  118. /**
  119. * Returns a QRMatrix object for the given $data and current QROptions
  120. *
  121. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  122. */
  123. public function getMatrix():QRMatrix{
  124. if(empty($this->dataSegments)){
  125. throw new QRCodeDataException('QRCode::getMatrix() No data given.');
  126. }
  127. $dataInterface = new QRData($this->options, $this->dataSegments);
  128. $maskPattern = $this->options->maskPattern === $this::MASK_PATTERN_AUTO
  129. ? MaskPattern::getBestPattern($dataInterface)
  130. : new MaskPattern($this->options->maskPattern);
  131. $matrix = $dataInterface->writeMatrix($maskPattern);
  132. // add matrix modifications after mask pattern evaluation and before handing over to output
  133. if($this->options->addLogoSpace){
  134. $matrix->setLogoSpace(
  135. $this->options->logoSpaceWidth,
  136. $this->options->logoSpaceHeight,
  137. $this->options->logoSpaceStartX,
  138. $this->options->logoSpaceStartY
  139. );
  140. }
  141. if($this->options->addQuietzone){
  142. $matrix->setQuietZone($this->options->quietzoneSize);
  143. }
  144. return $matrix;
  145. }
  146. /**
  147. * returns a fresh (built-in) QROutputInterface
  148. *
  149. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  150. */
  151. protected function initOutputInterface():QROutputInterface{
  152. if($this->options->outputType === $this::OUTPUT_CUSTOM){
  153. return $this->initCustomOutputInterface();
  154. }
  155. $outputInterface = $this::OUTPUT_MODES[$this->options->outputType] ?? false;
  156. if($outputInterface){
  157. return new $outputInterface($this->options, $this->getMatrix());
  158. }
  159. throw new QRCodeOutputException('invalid output type');
  160. }
  161. /**
  162. * initializes a custom output module after checking the existence of the class and if it implemnts the required interface
  163. *
  164. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  165. */
  166. protected function initCustomOutputInterface():QROutputInterface{
  167. if(!class_exists($this->options->outputInterface)){
  168. throw new QRCodeOutputException('invalid custom output module');
  169. }
  170. if(!in_array(QROutputInterface::class, class_implements($this->options->outputInterface))){
  171. throw new QRCodeOutputException('custom output module does not implement QROutputInterface');
  172. }
  173. /** @phan-suppress-next-line PhanTypeExpectedObjectOrClassName */
  174. return new $this->options->outputInterface($this->options, $this->getMatrix());
  175. }
  176. /**
  177. * checks if a string qualifies as numeric (convenience method)
  178. *
  179. * @deprecated
  180. * @codeCoverageIgnore
  181. */
  182. public function isNumber(string $string):bool{
  183. return Number::validateString($string);
  184. }
  185. /**
  186. * checks if a string qualifies as alphanumeric (convenience method)
  187. *
  188. * @deprecated
  189. * @codeCoverageIgnore
  190. */
  191. public function isAlphaNum(string $string):bool{
  192. return AlphaNum::validateString($string);
  193. }
  194. /**
  195. * checks if a string qualifies as Kanji (convenience method)
  196. *
  197. * @deprecated
  198. * @codeCoverageIgnore
  199. */
  200. public function isKanji(string $string):bool{
  201. return Kanji::validateString($string);
  202. }
  203. /**
  204. * a dummy (convenience method)
  205. *
  206. * @deprecated
  207. * @codeCoverageIgnore
  208. */
  209. public function isByte(string $string):bool{
  210. return Byte::validateString($string);
  211. }
  212. /**
  213. * Adds a data segment
  214. *
  215. * ISO/IEC 18004:2000 8.3.6 - Mixing modes
  216. * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
  217. */
  218. protected function addSegment(QRDataModeInterface $segment):void{
  219. $this->dataSegments[] = $segment;
  220. }
  221. /**
  222. * Clears the data segments array
  223. */
  224. public function clearSegments():self{
  225. $this->dataSegments = [];
  226. return $this;
  227. }
  228. /**
  229. * Adds a numeric data segment
  230. *
  231. * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
  232. */
  233. public function addNumericSegment(string $data):self{
  234. $this->addSegment(new Number($data));
  235. return $this;
  236. }
  237. /**
  238. * Adds an alphanumeric data segment
  239. *
  240. * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
  241. */
  242. public function addAlphaNumSegment(string $data):self{
  243. $this->addSegment(new AlphaNum($data));
  244. return $this;
  245. }
  246. /**
  247. * Adds a Kanji data segment
  248. *
  249. * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
  250. */
  251. public function addKanjiSegment(string $data):self{
  252. $this->addSegment(new Kanji($data));
  253. return $this;
  254. }
  255. /**
  256. * Adds an 8-bit byte data segment
  257. *
  258. * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
  259. */
  260. public function addByteSegment(string $data):self{
  261. $this->addSegment(new Byte($data));
  262. return $this;
  263. }
  264. /**
  265. * Adds a standalone ECI designator
  266. *
  267. * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
  268. */
  269. public function addEciDesignator(int $encoding):self{
  270. $this->addSegment(new ECI($encoding));
  271. return $this;
  272. }
  273. /**
  274. * Adds an ECI data segment (including designator)
  275. *
  276. * i hate this somehow but i'll leave it for now
  277. *
  278. * @throws \chillerlan\QRCode\QRCodeException
  279. */
  280. public function addEciSegment(int $encoding, string $data):self{
  281. // validate the encoding id
  282. $eciCharset = new ECICharset($encoding);
  283. // get charset name
  284. $eciCharsetName = $eciCharset->getName();
  285. // convert the string to the given charset
  286. if($eciCharsetName !== null){
  287. $data = mb_convert_encoding($data, $eciCharsetName, mb_detect_encoding($data));
  288. // add ECI designator
  289. $this->addSegment(new ECI($eciCharset->getID()));
  290. $this->addSegment(new Byte($data));
  291. return $this;
  292. }
  293. throw new QRCodeException('unable to add ECI segment');
  294. }
  295. /**
  296. * Reads a QR Code from a given file
  297. */
  298. public function readFromFile(string $path):DecoderResult{
  299. return $this->readFromSource($this->options->getLuminanceSourceFQCN()::fromFile($path, $this->options));
  300. }
  301. /**
  302. * Reads a QR Code from the given data blob
  303. */
  304. public function readFromBlob(string $blob):DecoderResult{
  305. return $this->readFromSource($this->options->getLuminanceSourceFQCN()::fromBlob($blob, $this->options));
  306. }
  307. /**
  308. * Reads a QR Code from the given luminance source
  309. */
  310. public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
  311. return (new Decoder)->decode($source);
  312. }
  313. }