QRCode.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  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\{
  14. ECICharset, GDLuminanceSource, IMagickLuminanceSource, LuminanceSourceInterface, MaskPattern, Mode
  15. };
  16. use chillerlan\QRCode\Data\{AlphaNum, Byte, ECI, Hanzi, Kanji, Number, QRData, QRDataModeInterface, QRMatrix};
  17. use chillerlan\QRCode\Decoder\{Decoder, DecoderResult};
  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. * The settings container
  33. */
  34. protected SettingsContainerInterface|QROptions $options;
  35. /**
  36. * A collection of one or more data segments of QRDataModeInterface instances to write
  37. *
  38. * @var \chillerlan\QRCode\Data\QRDataModeInterface[]
  39. */
  40. protected array $dataSegments = [];
  41. /**
  42. * The luminance source for the reader
  43. */
  44. protected string $luminanceSourceFQN = GDLuminanceSource::class;
  45. /**
  46. * QRCode constructor.
  47. *
  48. * PHP8: accept iterable
  49. */
  50. public function __construct(SettingsContainerInterface|QROptions $options = new QROptions){
  51. $this->setOptions($options);
  52. }
  53. /**
  54. * Sets an options instance
  55. */
  56. public function setOptions(SettingsContainerInterface|QROptions $options):static{
  57. $this->options = $options;
  58. if($this->options->readerUseImagickIfAvailable){
  59. $this->luminanceSourceFQN = IMagickLuminanceSource::class;
  60. }
  61. return $this;
  62. }
  63. /**
  64. * Renders a QR Code for the given $data and QROptions, saves $file optionally
  65. */
  66. public function render(string $data = null, string $file = null):mixed{
  67. if($data !== null){
  68. /** @var \chillerlan\QRCode\Data\QRDataModeInterface $dataInterface */
  69. foreach(Mode::INTERFACES as $dataInterface){
  70. if($dataInterface::validateString($data)){
  71. $this->addSegment(new $dataInterface($data));
  72. break;
  73. }
  74. }
  75. }
  76. return $this->renderMatrix($this->getQRMatrix(), $file);
  77. }
  78. /**
  79. * Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally
  80. */
  81. public function renderMatrix(QRMatrix $matrix, string $file = null):mixed{
  82. return $this->initOutputInterface($matrix)->dump($file ?? $this->options->cachefile);
  83. }
  84. /**
  85. * Returns a QRMatrix object for the given $data and current QROptions
  86. *
  87. * @throws \chillerlan\QRCode\Data\QRCodeDataException
  88. */
  89. public function getQRMatrix():QRMatrix{
  90. $matrix = (new QRData($this->options, $this->dataSegments))->writeMatrix();
  91. $maskPattern = $this->options->maskPattern === MaskPattern::AUTO
  92. ? MaskPattern::getBestPattern($matrix)
  93. : new MaskPattern($this->options->maskPattern);
  94. $matrix->setFormatInfo($maskPattern)->mask($maskPattern);
  95. return $this->addMatrixModifications($matrix);
  96. }
  97. /**
  98. * add matrix modifications after mask pattern evaluation and before handing over to output
  99. */
  100. protected function addMatrixModifications(QRMatrix $matrix):QRMatrix{
  101. if($this->options->addLogoSpace){
  102. // check whether one of the dimensions was omitted
  103. $logoSpaceWidth = ($this->options->logoSpaceWidth ?? $this->options->logoSpaceHeight ?? 0);
  104. $logoSpaceHeight = ($this->options->logoSpaceHeight ?? $logoSpaceWidth);
  105. $matrix->setLogoSpace(
  106. $logoSpaceWidth,
  107. $logoSpaceHeight,
  108. $this->options->logoSpaceStartX,
  109. $this->options->logoSpaceStartY
  110. );
  111. }
  112. if($this->options->addQuietzone){
  113. $matrix->setQuietZone($this->options->quietzoneSize);
  114. }
  115. return $matrix;
  116. }
  117. /**
  118. * initializes a fresh built-in or custom QROutputInterface
  119. *
  120. * @throws \chillerlan\QRCode\Output\QRCodeOutputException
  121. */
  122. protected function initOutputInterface(QRMatrix $matrix):QROutputInterface{
  123. $outputInterface = $this->options->outputInterface;
  124. if(empty($outputInterface) || !class_exists($outputInterface)){
  125. throw new QRCodeOutputException('invalid output module');
  126. }
  127. if(!in_array(QROutputInterface::class, class_implements($outputInterface))){
  128. throw new QRCodeOutputException('output module does not implement QROutputInterface');
  129. }
  130. return new $outputInterface($this->options, $matrix);
  131. }
  132. /**
  133. * Adds a data segment
  134. *
  135. * ISO/IEC 18004:2000 8.3.6 - Mixing modes
  136. * ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
  137. */
  138. public function addSegment(QRDataModeInterface $segment):static{
  139. $this->dataSegments[] = $segment;
  140. return $this;
  141. }
  142. /**
  143. * Clears the data segments array
  144. *
  145. * @codeCoverageIgnore
  146. */
  147. public function clearSegments():static{
  148. $this->dataSegments = [];
  149. return $this;
  150. }
  151. /**
  152. * Adds a numeric data segment
  153. *
  154. * ISO/IEC 18004:2000 8.3.2 - Numeric Mode
  155. */
  156. public function addNumericSegment(string $data):static{
  157. return $this->addSegment(new Number($data));
  158. }
  159. /**
  160. * Adds an alphanumeric data segment
  161. *
  162. * ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
  163. */
  164. public function addAlphaNumSegment(string $data):static{
  165. return $this->addSegment(new AlphaNum($data));
  166. }
  167. /**
  168. * Adds a Kanji data segment (Japanese 13-bit double-byte characters, Shift-JIS)
  169. *
  170. * ISO/IEC 18004:2000 8.3.5 - Kanji Mode
  171. */
  172. public function addKanjiSegment(string $data):static{
  173. return $this->addSegment(new Kanji($data));
  174. }
  175. /**
  176. * Adds a Hanzi data segment (simplified Chinese 13-bit double-byte characters, GB2312/GB18030)
  177. *
  178. * GBT18284-2000 Hanzi Mode
  179. */
  180. public function addHanziSegment(string $data):static{
  181. return $this->addSegment(new Hanzi($data));
  182. }
  183. /**
  184. * Adds an 8-bit byte data segment
  185. *
  186. * ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
  187. */
  188. public function addByteSegment(string $data):static{
  189. return $this->addSegment(new Byte($data));
  190. }
  191. /**
  192. * Adds a standalone ECI designator
  193. *
  194. * The ECI designator must be followed by a Byte segment that contains the string encoded according to the given ECI charset
  195. *
  196. * ISO/IEC 18004:2000 8.3.1 - Extended Channel Interpretation (ECI) Mode
  197. */
  198. public function addEciDesignator(int $encoding):static{
  199. return $this->addSegment(new ECI($encoding));
  200. }
  201. /**
  202. * Adds an ECI data segment (including designator)
  203. *
  204. * The given string will be encoded from mb_internal_encoding() to the given ECI character set
  205. *
  206. * I hate this somehow, but I'll leave it for now
  207. *
  208. * @throws \chillerlan\QRCode\QRCodeException
  209. */
  210. public function addEciSegment(int $encoding, string $data):static{
  211. // validate the encoding id
  212. $eciCharset = new ECICharset($encoding);
  213. // get charset name
  214. $eciCharsetName = $eciCharset->getName();
  215. // convert the string to the given charset
  216. if($eciCharsetName !== null){
  217. $data = mb_convert_encoding($data, $eciCharsetName, mb_internal_encoding());
  218. return $this
  219. ->addEciDesignator($eciCharset->getID())
  220. ->addByteSegment($data)
  221. ;
  222. }
  223. throw new QRCodeException('unable to add ECI segment');
  224. }
  225. /**
  226. * Reads a QR Code from a given file
  227. *
  228. * @noinspection PhpUndefinedMethodInspection
  229. */
  230. public function readFromFile(string $path):DecoderResult{
  231. return $this->readFromSource($this->luminanceSourceFQN::fromFile($path, $this->options));
  232. }
  233. /**
  234. * Reads a QR Code from the given data blob
  235. *
  236. * @noinspection PhpUndefinedMethodInspection
  237. */
  238. public function readFromBlob(string $blob):DecoderResult{
  239. return $this->readFromSource($this->luminanceSourceFQN::fromBlob($blob, $this->options));
  240. }
  241. /**
  242. * Reads a QR Code from the given luminance source
  243. */
  244. public function readFromSource(LuminanceSourceInterface $source):DecoderResult{
  245. return (new Decoder($this->options))->decode($source);
  246. }
  247. }