QRCode.php 10 KB

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