QRCode.php 13 KB

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