QRCode.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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 [classname, data] to write
  145. *
  146. * @see \chillerlan\QRCode\Data\QRDataModeInterface
  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. * Sets the options instance
  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->getMatrix(), $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);
  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 getMatrix():QRMatrix{
  204. $dataInterface = new QRData($this->options, $this->dataSegments);
  205. $maskPattern = $this->options->maskPattern === MaskPattern::AUTO
  206. ? MaskPattern::getBestPattern($dataInterface)
  207. : new MaskPattern($this->options->maskPattern);
  208. $matrix = $dataInterface->writeMatrix($maskPattern);
  209. // add matrix modifications after mask pattern evaluation and before handing over to output
  210. if($this->options->addLogoSpace){
  211. $logoSpaceWidth = $this->options->logoSpaceWidth;
  212. $logoSpaceHeight = $this->options->logoSpaceHeight;
  213. // check whether one of the dimensions was omitted
  214. if($logoSpaceWidth === null || $logoSpaceHeight === null){
  215. $logoSpaceWidth = $logoSpaceWidth ?? $logoSpaceHeight ?? 0;
  216. $logoSpaceHeight = null;
  217. }
  218. $matrix->setLogoSpace(
  219. $logoSpaceWidth,
  220. $logoSpaceHeight,
  221. $this->options->logoSpaceStartX,
  222. $this->options->logoSpaceStartY
  223. );
  224. }
  225. if($this->options->addQuietzone){
  226. $matrix->setQuietZone($this->options->quietzoneSize);
  227. }
  228. return $matrix;
  229. }
  230. /**
  231. * initializes a fresh built-in or custom QROutputInterface
  232. *
  233. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  234. */
  235. protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{
  236. $outputInterface = QROutputInterface::MODES[$this->options->outputType] ?? null;
  237. if($this->options->outputType === QROutputInterface::CUSTOM){
  238. $outputInterface = $this->options->outputInterface;
  239. }
  240. if(!$outputInterface || !class_exists($outputInterface)){
  241. throw new QRCodeOutputException('invalid output module');
  242. }
  243. if(!in_array(QROutputInterface::class, class_implements($outputInterface))){
  244. throw new QRCodeOutputException('output module does not implement QROutputInterface');
  245. }
  246. return new $outputInterface($this->options, $matrix);
  247. }
  248. /**
  249. * checks if a string qualifies as numeric (convenience method)
  250. *
  251. * @deprecated 5.0.0 use Number::validateString() instead
  252. * @see \chillerlan\QRCode\Data\Number::validateString()
  253. * @codeCoverageIgnore
  254. */
  255. public function isNumber(string $string):bool{
  256. return Number::validateString($string);
  257. }
  258. /**
  259. * checks if a string qualifies as alphanumeric (convenience method)
  260. *
  261. * @deprecated 5.0.0 use AlphaNum::validateString() instead
  262. * @see \chillerlan\QRCode\Data\AlphaNum::validateString()
  263. * @codeCoverageIgnore
  264. */
  265. public function isAlphaNum(string $string):bool{
  266. return AlphaNum::validateString($string);
  267. }
  268. /**
  269. * checks if a string qualifies as Kanji (convenience method)
  270. *
  271. * @deprecated 5.0.0 use Kanji::validateString() instead
  272. * @see \chillerlan\QRCode\Data\Kanji::validateString()
  273. * @codeCoverageIgnore
  274. */
  275. public function isKanji(string $string):bool{
  276. return Kanji::validateString($string);
  277. }
  278. /**
  279. * a dummy (convenience method)
  280. *
  281. * @deprecated 5.0.0 use Byte::validateString() instead
  282. * @see \chillerlan\QRCode\Data\Byte::validateString()
  283. * @codeCoverageIgnore
  284. */
  285. public function isByte(string $string):bool{
  286. return Byte::validateString($string);
  287. }
  288. /**
  289. * Adds a data segment
  290. *
  291. * ISO/IEC 18004:2000 8.3.6 - Mixing modes
  292. * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
  293. */
  294. public function addSegment(QRDataModeInterface $segment):void{
  295. $this->dataSegments[] = $segment;
  296. }
  297. /**
  298. * Clears the data segments array
  299. */
  300. public function clearSegments():self{
  301. $this->dataSegments = [];
  302. return $this;
  303. }
  304. /**
  305. * Adds a numeric data segment
  306. *
  307. * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
  308. */
  309. public function addNumericSegment(string $data):self{
  310. $this->addSegment(new Number($data));
  311. return $this;
  312. }
  313. /**
  314. * Adds an alphanumeric data segment
  315. *
  316. * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
  317. */
  318. public function addAlphaNumSegment(string $data):self{
  319. $this->addSegment(new AlphaNum($data));
  320. return $this;
  321. }
  322. /**
  323. * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS)
  324. *
  325. * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
  326. */
  327. public function addKanjiSegment(string $data):self{
  328. $this->addSegment(new Kanji($data));
  329. return $this;
  330. }
  331. /**
  332. * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030)
  333. *
  334. * GBT18284-2000 Hanzi Mode
  335. */
  336. public function addHanziSegment(string $data):self{
  337. $this->addSegment(new Hanzi($data));
  338. return $this;
  339. }
  340. /**
  341. * Adds an 8-bit byte data segment
  342. *
  343. * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
  344. */
  345. public function addByteSegment(string $data):self{
  346. $this->addSegment(new Byte($data));
  347. return $this;
  348. }
  349. /**
  350. * Adds a standalone ECI designator
  351. *
  352. * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset
  353. *
  354. * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
  355. */
  356. public function addEciDesignator(int $encoding):self{
  357. $this->addSegment(new ECI($encoding));
  358. return $this;
  359. }
  360. /**
  361. * Adds an ECI data segment (including designator)
  362. *
  363. * The given string will be encoded from mb_internal_encoding() to the given ECI character set
  364. *
  365. * I hate this somehow, but I'll leave it for now
  366. *
  367. * @throws \chillerlan\QRCode\QRCodeException
  368. */
  369. public function addEciSegment(int $encoding, string $data):self{
  370. // validate the encoding id
  371. $eciCharset = new ECICharset($encoding);
  372. // get charset name
  373. $eciCharsetName = $eciCharset->getName();
  374. // convert the string to the given charset
  375. if($eciCharsetName !== null){
  376. $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding());
  377. // add ECI designator
  378. $this->addSegment(new ECI($eciCharset->getID()));
  379. $this->addSegment(new Byte($data));
  380. return $this;
  381. }
  382. throw new QRCodeException('unable to add ECI segment');
  383. }
  384. /**
  385. * Reads a QR Code from a given file
  386. *
  387. * @noinspection PhpUndefinedMethodInspection
  388. */
  389. public function readFromFile(string $path):DecoderResult{
  390. return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options));
  391. }
  392. /**
  393. * Reads a QR Code from the given data blob
  394. *
  395. * @noinspection PhpUndefinedMethodInspection
  396. */
  397. public function readFromBlob(string $blob):DecoderResult{
  398. return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options));
  399. }
  400. /**
  401. * Reads a QR Code from the given luminance source
  402. */
  403. public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
  404. return (new Decoder)->decode($source);
  405. }
  406. }