QRCode.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  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. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  11. */
  12. namespace chillerlan\QRCode;
  13. use chillerlan\QRCode\Common\{EccLevel, ECICharset, MaskPattern, Mode, Version};
  14. use chillerlan\QRCode\Data\{
  15. AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRData, QRDataModeInterface, QRMatrix
  16. };
  17. use chillerlan\QRCode\Decoder\{Decoder, DecoderResult, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface};
  18. use chillerlan\QRCode\Output\{QRCodeOutputException, QROutputInterface};
  19. use chillerlan\Settings\SettingsContainerInterface;
  20. use function class_exists, class_implements, in_array, mb_convert_encoding, mb_internal_encoding;
  21. /**
  22. * Turns a text string into a Model 2 QR Code
  23. *
  24. * @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
  25. * @see https://www.qrcode.com/en/codes/model12.html
  26. * @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
  27. * @see https://en.wikipedia.org/wiki/QR_code
  28. * @see https://www.thonky.com/qr-code-tutorial/
  29. */
  30. class QRCode{
  31. /**
  32. * @deprecated 5.0.0 use Version::AUTO instead
  33. * @see \chillerlan\QRCode\Common\Version::AUTO
  34. * @var int
  35. */
  36. public const VERSION_AUTO = Version::AUTO;
  37. /**
  38. * @deprecated 5.0.0 use MaskPattern::AUTO instead
  39. * @see \chillerlan\QRCode\Common\MaskPattern::AUTO
  40. * @var int
  41. */
  42. public const MASK_PATTERN_AUTO = MaskPattern::AUTO;
  43. /**
  44. * @deprecated 5.0.0 use EccLevel::L instead
  45. * @see \chillerlan\QRCode\Common\EccLevel::L
  46. * @var int
  47. */
  48. public const ECC_L = EccLevel::L;
  49. /**
  50. * @deprecated 5.0.0 use EccLevel::M instead
  51. * @see \chillerlan\QRCode\Common\EccLevel::M
  52. * @var int
  53. */
  54. public const ECC_M = EccLevel::M;
  55. /**
  56. * @deprecated 5.0.0 use EccLevel::Q instead
  57. * @see \chillerlan\QRCode\Common\EccLevel::Q
  58. * @var int
  59. */
  60. public const ECC_Q = EccLevel::Q;
  61. /**
  62. * @deprecated 5.0.0 use EccLevel::H instead
  63. * @see \chillerlan\QRCode\Common\EccLevel::H
  64. * @var int
  65. */
  66. public const ECC_H = EccLevel::H;
  67. /**
  68. * @deprecated 5.0.0 use QROutputInterface::MARKUP_HTML instead
  69. * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_HTML
  70. * @var string
  71. */
  72. public const OUTPUT_MARKUP_HTML = QROutputInterface::MARKUP_HTML;
  73. /**
  74. * @deprecated 5.0.0 use QROutputInterface::MARKUP_SVG instead
  75. * @see \chillerlan\QRCode\Output\QROutputInterface::MARKUP_SVG
  76. * @var string
  77. */
  78. public const OUTPUT_MARKUP_SVG = QROutputInterface::MARKUP_SVG;
  79. /**
  80. * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_PNG instead
  81. * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_PNG
  82. * @var string
  83. */
  84. public const OUTPUT_IMAGE_PNG = QROutputInterface::GDIMAGE_PNG;
  85. /**
  86. * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_JPG instead
  87. * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_JPG
  88. * @var string
  89. */
  90. public const OUTPUT_IMAGE_JPG = QROutputInterface::GDIMAGE_JPG;
  91. /**
  92. * @deprecated 5.0.0 use QROutputInterface::GDIMAGE_GIF instead
  93. * @see \chillerlan\QRCode\Output\QROutputInterface::GDIMAGE_GIF
  94. * @var string
  95. */
  96. public const OUTPUT_IMAGE_GIF = QROutputInterface::GDIMAGE_GIF;
  97. /**
  98. * @deprecated 5.0.0 use QROutputInterface::STRING_JSON instead
  99. * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_JSON
  100. * @var string
  101. */
  102. public const OUTPUT_STRING_JSON = QROutputInterface::STRING_JSON;
  103. /**
  104. * @deprecated 5.0.0 use QROutputInterface::STRING_TEXT instead
  105. * @see \chillerlan\QRCode\Output\QROutputInterface::STRING_TEXT
  106. * @var string
  107. */
  108. public const OUTPUT_STRING_TEXT = QROutputInterface::STRING_TEXT;
  109. /**
  110. * @deprecated 5.0.0 use QROutputInterface::IMAGICK instead
  111. * @see \chillerlan\QRCode\Output\QROutputInterface::IMAGICK
  112. * @var string
  113. */
  114. public const OUTPUT_IMAGICK = QROutputInterface::IMAGICK;
  115. /**
  116. * @deprecated 5.0.0 use QROutputInterface::FPDF instead
  117. * @see \chillerlan\QRCode\Output\QROutputInterface::FPDF
  118. * @var string
  119. */
  120. public const OUTPUT_FPDF = QROutputInterface::FPDF;
  121. /**
  122. * @deprecated 5.0.0 use QROutputInterface::EPS instead
  123. * @see \chillerlan\QRCode\Output\QROutputInterface::EPS
  124. * @var string
  125. */
  126. public const OUTPUT_EPS = QROutputInterface::EPS;
  127. /**
  128. * @deprecated 5.0.0 use QROutputInterface::CUSTOM instead
  129. * @see \chillerlan\QRCode\Output\QROutputInterface::CUSTOM
  130. * @var string
  131. */
  132. public const OUTPUT_CUSTOM = QROutputInterface::CUSTOM;
  133. /**
  134. * @deprecated 5.0.0 use QROutputInterface::MODES instead
  135. * @see \chillerlan\QRCode\Output\QROutputInterface::MODES
  136. * @var string[]
  137. */
  138. public const OUTPUT_MODES = QROutputInterface::MODES;
  139. /**
  140. * The settings container
  141. *
  142. * @var \chillerlan\QRCode\QROptions|\chillerlan\Settings\SettingsContainerInterface
  143. */
  144. protected SettingsContainerInterface $options;
  145. /**
  146. * A collection of one or more data segments of QRDataModeInterface instances to write
  147. *
  148. * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
  149. */
  150. protected array $dataSegments = [];
  151. /**
  152. * The luminance source for the reader
  153. */
  154. protected string $luminanceSourceFQN = GDLuminanceSource::class;
  155. /**
  156. * QRCode constructor.
  157. *
  158. * PHP8: accept iterable
  159. */
  160. public function __construct(SettingsContainerInterface $options = null){
  161. $this->setOptions(($options ?? new QROptions));
  162. }
  163. /**
  164. * Sets an options instance
  165. */
  166. public function setOptions(SettingsContainerInterface $options):self{
  167. $this->options = $options;
  168. if($this->options->readerUseImagickIfAvailable){
  169. $this->luminanceSourceFQN = IMagickLuminanceSource::class;
  170. }
  171. return $this;
  172. }
  173. /**
  174. * Renders a QR Code for the given $data and QROptions, saves $file optionally
  175. *
  176. * @return mixed
  177. */
  178. public function render(string $data = null, string $file = null){
  179. if($data !== null){
  180. /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
  181. foreach(Mode::INTERFACES as $dataInterface){
  182. if($dataInterface::validateString($data)){
  183. $this->addSegment(new $dataInterface($data));
  184. break;
  185. }
  186. }
  187. }
  188. return $this->renderMatrix($this->getQRMatrix(), $file);
  189. }
  190. /**
  191. * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally
  192. *
  193. * @return mixed
  194. */
  195. public function renderMatrix(QRMatrix $matrix, string $file = null){
  196. return $this->initOutputInterface($matrix)->dump($file ?? $this->options->cachefile);
  197. }
  198. /**
  199. * Returns a QRMatrix object for the given $data and current QROptions
  200. *
  201. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  202. */
  203. public function getQRMatrix():QRMatrix{
  204. $matrix = (new QRData($this->options, $this->dataSegments))->writeMatrix();
  205. $maskPattern = $this->options->maskPattern === MaskPattern::AUTO
  206. ? MaskPattern::getBestPattern($matrix)
  207. : new MaskPattern($this->options->maskPattern);
  208. $matrix->setFormatInfo($maskPattern)->mask($maskPattern);
  209. return $this->addMatrixModifications($matrix);
  210. }
  211. /**
  212. * add matrix modifications after mask pattern evaluation and before handing over to output
  213. */
  214. protected function addMatrixModifications(QRMatrix $matrix):QRMatrix{
  215. if($this->options->addLogoSpace){
  216. $logoSpaceWidth = $this->options->logoSpaceWidth;
  217. $logoSpaceHeight = $this->options->logoSpaceHeight;
  218. // check whether one of the dimensions was omitted
  219. if($logoSpaceWidth === null || $logoSpaceHeight === null){
  220. $logoSpaceWidth = ($logoSpaceWidth ?? $logoSpaceHeight ?? 0);
  221. $logoSpaceHeight = null;
  222. }
  223. $matrix->setLogoSpace(
  224. $logoSpaceWidth,
  225. $logoSpaceHeight,
  226. $this->options->logoSpaceStartX,
  227. $this->options->logoSpaceStartY
  228. );
  229. }
  230. if($this->options->addQuietzone){
  231. $matrix->setQuietZone($this->options->quietzoneSize);
  232. }
  233. return $matrix;
  234. }
  235. /**
  236. * @deprecated 5.0.0 use QRCode::getQRMatrix() instead
  237. * @see \chillerlan\QRCode\QRCode::getQRMatrix()
  238. * @codeCoverageIgnore
  239. */
  240. public function getMatrix():QRMatrix{
  241. return $this->getQRMatrix();
  242. }
  243. /**
  244. * initializes a fresh built-in or custom QROutputInterface
  245. *
  246. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  247. */
  248. protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{
  249. $outputInterface =( QROutputInterface::MODES[$this->options->outputType] ?? null);
  250. if($this->options->outputType === QROutputInterface::CUSTOM){
  251. $outputInterface = $this->options->outputInterface;
  252. }
  253. if(!$outputInterface || !class_exists($outputInterface)){
  254. throw new QRCodeOutputException('invalid output module');
  255. }
  256. if(!in_array(QROutputInterface::class, class_implements($outputInterface))){
  257. throw new QRCodeOutputException('output module does not implement QROutputInterface');
  258. }
  259. return new $outputInterface($this->options, $matrix);
  260. }
  261. /**
  262. * checks if a string qualifies as numeric (convenience method)
  263. *
  264. * @deprecated 5.0.0 use Number::validateString() instead
  265. * @see \chillerlan\QRCode\Data\Number::validateString()
  266. * @codeCoverageIgnore
  267. */
  268. public function isNumber(string $string):bool{
  269. return Number::validateString($string);
  270. }
  271. /**
  272. * checks if a string qualifies as alphanumeric (convenience method)
  273. *
  274. * @deprecated 5.0.0 use AlphaNum::validateString() instead
  275. * @see \chillerlan\QRCode\Data\AlphaNum::validateString()
  276. * @codeCoverageIgnore
  277. */
  278. public function isAlphaNum(string $string):bool{
  279. return AlphaNum::validateString($string);
  280. }
  281. /**
  282. * checks if a string qualifies as Kanji (convenience method)
  283. *
  284. * @deprecated 5.0.0 use Kanji::validateString() instead
  285. * @see \chillerlan\QRCode\Data\Kanji::validateString()
  286. * @codeCoverageIgnore
  287. */
  288. public function isKanji(string $string):bool{
  289. return Kanji::validateString($string);
  290. }
  291. /**
  292. * a dummy (convenience method)
  293. *
  294. * @deprecated 5.0.0 use Byte::validateString() instead
  295. * @see \chillerlan\QRCode\Data\Byte::validateString()
  296. * @codeCoverageIgnore
  297. */
  298. public function isByte(string $string):bool{
  299. return Byte::validateString($string);
  300. }
  301. /**
  302. * Adds a data segment
  303. *
  304. * ISO/IEC 18004:2000 8.3.6 - Mixing modes
  305. * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
  306. */
  307. public function addSegment(QRDataModeInterface $segment):self{
  308. $this->dataSegments[] = $segment;
  309. return $this;
  310. }
  311. /**
  312. * Clears the data segments array
  313. *
  314. * @codeCoverageIgnore
  315. */
  316. public function clearSegments():self{
  317. $this->dataSegments = [];
  318. return $this;
  319. }
  320. /**
  321. * Adds a numeric data segment
  322. *
  323. * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
  324. */
  325. public function addNumericSegment(string $data):self{
  326. return $this->addSegment(new Number($data));
  327. }
  328. /**
  329. * Adds an alphanumeric data segment
  330. *
  331. * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
  332. */
  333. public function addAlphaNumSegment(string $data):self{
  334. return $this->addSegment(new AlphaNum($data));
  335. }
  336. /**
  337. * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS)
  338. *
  339. * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
  340. */
  341. public function addKanjiSegment(string $data):self{
  342. return $this->addSegment(new Kanji($data));
  343. }
  344. /**
  345. * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030)
  346. *
  347. * GBT18284-2000 Hanzi Mode
  348. */
  349. public function addHanziSegment(string $data):self{
  350. return $this->addSegment(new Hanzi($data));
  351. }
  352. /**
  353. * Adds an 8-bit byte data segment
  354. *
  355. * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
  356. */
  357. public function addByteSegment(string $data):self{
  358. return $this->addSegment(new Byte($data));
  359. }
  360. /**
  361. * Adds a standalone ECI designator
  362. *
  363. * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset
  364. *
  365. * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
  366. */
  367. public function addEciDesignator(int $encoding):self{
  368. return $this->addSegment(new ECI($encoding));
  369. }
  370. /**
  371. * Adds an ECI data segment (including designator)
  372. *
  373. * The given string will be encoded from mb_internal_encoding() to the given ECI character set
  374. *
  375. * I hate this somehow, but I'll leave it for now
  376. *
  377. * @throws \chillerlan\QRCode\QRCodeException
  378. */
  379. public function addEciSegment(int $encoding, string $data):self{
  380. // validate the encoding id
  381. $eciCharset = new ECICharset($encoding);
  382. // get charset name
  383. $eciCharsetName = $eciCharset->getName();
  384. // convert the string to the given charset
  385. if($eciCharsetName !== null){
  386. $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding());
  387. return $this
  388. ->addEciDesignator($eciCharset->getID())
  389. ->addByteSegment($data)
  390. ;
  391. }
  392. throw new QRCodeException('unable to add ECI segment');
  393. }
  394. /**
  395. * Reads a QR Code from a given file
  396. *
  397. * @noinspection PhpUndefinedMethodInspection
  398. */
  399. public function readFromFile(string $path):DecoderResult{
  400. return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options));
  401. }
  402. /**
  403. * Reads a QR Code from the given data blob
  404. *
  405. * @noinspection PhpUndefinedMethodInspection
  406. */
  407. public function readFromBlob(string $blob):DecoderResult{
  408. return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options));
  409. }
  410. /**
  411. * Reads a QR Code from the given luminance source
  412. */
  413. public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
  414. return (new Decoder)->decode($source);
  415. }
  416. }