Generator.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. <?php
  2. namespace SimpleSoftwareIO\QrCode;
  3. use BaconQrCode\Common\ErrorCorrectionLevel;
  4. use BaconQrCode\Encoder\Encoder;
  5. use BaconQrCode\Exception\WriterException;
  6. use BaconQrCode\Renderer\Color\Alpha;
  7. use BaconQrCode\Renderer\Color\ColorInterface;
  8. use BaconQrCode\Renderer\Color\Rgb;
  9. use BaconQrCode\Renderer\Eye\EyeInterface;
  10. use BaconQrCode\Renderer\Eye\ModuleEye;
  11. use BaconQrCode\Renderer\Eye\SimpleCircleEye;
  12. use BaconQrCode\Renderer\Eye\SquareEye;
  13. use BaconQrCode\Renderer\Image\EpsImageBackEnd;
  14. use BaconQrCode\Renderer\Image\ImageBackEndInterface;
  15. use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
  16. use BaconQrCode\Renderer\Image\SvgImageBackEnd;
  17. use BaconQrCode\Renderer\ImageRenderer;
  18. use BaconQrCode\Renderer\Module\DotsModule;
  19. use BaconQrCode\Renderer\Module\ModuleInterface;
  20. use BaconQrCode\Renderer\Module\RoundnessModule;
  21. use BaconQrCode\Renderer\Module\SquareModule;
  22. use BaconQrCode\Renderer\RendererStyle\EyeFill;
  23. use BaconQrCode\Renderer\RendererStyle\Fill;
  24. use BaconQrCode\Renderer\RendererStyle\Gradient;
  25. use BaconQrCode\Renderer\RendererStyle\GradientType;
  26. use BaconQrCode\Renderer\RendererStyle\RendererStyle;
  27. use BaconQrCode\Writer;
  28. use BadMethodCallException;
  29. use InvalidArgumentException;
  30. use SimpleSoftwareIO\QrCode\DataTypes\DataTypeInterface;
  31. class Generator
  32. {
  33. /**
  34. * Holds the selected formatter.
  35. *
  36. * @var string
  37. */
  38. protected $format = 'svg';
  39. /**
  40. * Holds the size of the QrCode in pixels.
  41. *
  42. * @var int
  43. */
  44. protected $pixels = 100;
  45. /**
  46. * Holds the margin size of the QrCode.
  47. *
  48. * @var int
  49. */
  50. protected $margin = 0;
  51. /**
  52. * Holds the selected error correction.
  53. * L: 7% loss.
  54. * M: 15% loss.
  55. * Q: 25% loss.
  56. * H: 30% loss.
  57. *
  58. * @var string|null
  59. */
  60. protected $errorCorrection = null;
  61. /**
  62. * Holds the selected encoder. Possible values are
  63. * ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6,
  64. * ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-8859-10, ISO-8859-11,
  65. * ISO-8859-12, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16,
  66. * SHIFT-JIS, WINDOWS-1250, WINDOWS-1251, WINDOWS-1252, WINDOWS-1256,
  67. * UTF-16BE, UTF-8, ASCII, GBK, EUC-KR.
  68. *
  69. * @var string
  70. */
  71. protected $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING;
  72. /**
  73. * The style of the blocks within the QrCode.
  74. * Possible values are square, dot, and round.
  75. *
  76. * @var string
  77. */
  78. protected $style = 'square';
  79. /**
  80. * The size of the selected style between 0 and 1.
  81. * This only applies to dot and round.
  82. *
  83. * @var float|null
  84. */
  85. protected $styleSize = null;
  86. /**
  87. * The style to apply to the eye.
  88. * Possible values are circle and square.
  89. *
  90. * @var string|null
  91. */
  92. protected $eyeStyle = null;
  93. /**
  94. * The foreground color of the QrCode.
  95. *
  96. * @var ColorInterface|null
  97. */
  98. protected $color = null;
  99. /**
  100. * The background color of the QrCode.
  101. *
  102. * @var ColorInterface|null
  103. */
  104. protected $backgroundColor = null;
  105. /**
  106. * An array that holds EyeFills of the color of the eyes.
  107. *
  108. * @var array
  109. */
  110. protected $eyeColors = [];
  111. /**
  112. * The gradient to apply to the QrCode.
  113. *
  114. * @var Gradient
  115. */
  116. protected $gradient;
  117. /**
  118. * Holds an image string that will be merged with the QrCode.
  119. *
  120. * @var null|string
  121. */
  122. protected $imageMerge = null;
  123. /**
  124. * The percentage that a merged image should take over the source image.
  125. *
  126. * @var float
  127. */
  128. protected $imagePercentage = .2;
  129. /**
  130. * Creates a new datatype object and then generates a QrCode.
  131. *
  132. * @param $method
  133. * @param array $arguments
  134. */
  135. public function __call($method, array $arguments)
  136. {
  137. $dataType = $this->createClass($method);
  138. $dataType->create($arguments);
  139. return $this->generate(strval($dataType));
  140. }
  141. /**
  142. * Generates the QrCode.
  143. *
  144. * @param string $text
  145. * @param string|null $filename
  146. * @return void|Illuminate\Support\HtmlString|string
  147. * @throws WriterException
  148. * @throws InvalidArgumentException
  149. */
  150. public function generate(string $text, string $filename = null)
  151. {
  152. $qrCode = $this->getWriter($this->getRenderer())->writeString($text, $this->encoding, $this->errorCorrection);
  153. if ($this->imageMerge !== null && $this->format === 'png') {
  154. $merger = new ImageMerge(new Image($qrCode), new Image($this->imageMerge));
  155. $qrCode = $merger->merge($this->imagePercentage);
  156. }
  157. if ($filename) {
  158. file_put_contents($filename, $qrCode);
  159. return;
  160. }
  161. if (class_exists(\Illuminate\Support\HtmlString::class)) {
  162. return new \Illuminate\Support\HtmlString($qrCode);
  163. }
  164. return $qrCode;
  165. }
  166. /**
  167. * Merges an image over the QrCode.
  168. *
  169. * @param string $filepath
  170. * @param float $percentage
  171. * @param SimpleSoftwareIO\QrCode\boolean|bool $absolute
  172. * @return Generator
  173. */
  174. public function merge(string $filepath, float $percentage = .2, bool $absolute = false): self
  175. {
  176. if (function_exists('base_path') && ! $absolute) {
  177. $filepath = base_path().$filepath;
  178. }
  179. $this->imageMerge = file_get_contents($filepath);
  180. $this->imagePercentage = $percentage;
  181. return $this;
  182. }
  183. /**
  184. * Merges an image string with the center of the QrCode.
  185. *
  186. * @param string $content
  187. * @param float $percentage
  188. * @return Generator
  189. */
  190. public function mergeString(string $content, float $percentage = .2): self
  191. {
  192. $this->imageMerge = $content;
  193. $this->imagePercentage = $percentage;
  194. return $this;
  195. }
  196. /**
  197. * Sets the size of the QrCode.
  198. *
  199. * @param int $pixels
  200. * @return Generator
  201. */
  202. public function size(int $pixels): self
  203. {
  204. $this->pixels = $pixels;
  205. return $this;
  206. }
  207. /**
  208. * Sets the format of the QrCode.
  209. *
  210. * @param string $format
  211. * @return Generator
  212. * @throws InvalidArgumentException
  213. */
  214. public function format(string $format): self
  215. {
  216. if (! in_array($format, ['svg', 'eps', 'png'])) {
  217. throw new InvalidArgumentException("\$format must be svg, eps, or png. {$format} is not a valid.");
  218. }
  219. $this->format = $format;
  220. return $this;
  221. }
  222. /**
  223. * Sets the foreground color of the QrCode.
  224. *
  225. * @param int $red
  226. * @param int $green
  227. * @param int $blue
  228. * @param null|int $alpha
  229. * @return Generator
  230. */
  231. public function color(int $red, int $green, int $blue, ?int $alpha = null): self
  232. {
  233. $this->color = $this->createColor($red, $green, $blue, $alpha);
  234. return $this;
  235. }
  236. /**
  237. * Sets the background color of the QrCode.
  238. *
  239. * @param int $red
  240. * @param int $green
  241. * @param int $blue
  242. * @param null|int $alpha
  243. * @return Generator
  244. */
  245. public function backgroundColor(int $red, int $green, int $blue, ?int $alpha = null): self
  246. {
  247. $this->backgroundColor = $this->createColor($red, $green, $blue, $alpha);
  248. return $this;
  249. }
  250. /**
  251. * Sets the eye color for the provided eye index.
  252. *
  253. * @param int $eyeNumber
  254. * @param int $innerRed
  255. * @param int $innerGreen
  256. * @param int $innerBlue
  257. * @param int $outterRed
  258. * @param int $outterGreen
  259. * @param int $outterBlue
  260. * @return Generator
  261. * @throws InvalidArgumentException
  262. */
  263. public function eyeColor(int $eyeNumber, int $innerRed, int $innerGreen, int $innerBlue, int $outterRed = 0, int $outterGreen = 0, int $outterBlue = 0): self
  264. {
  265. if ($eyeNumber < 0 || $eyeNumber > 2) {
  266. throw new InvalidArgumentException("\$eyeNumber must be 0, 1, or 2. {$eyeNumber} is not valid.");
  267. }
  268. $this->eyeColors[$eyeNumber] = new EyeFill(
  269. $this->createColor($innerRed, $innerGreen, $innerBlue),
  270. $this->createColor($outterRed, $outterGreen, $outterBlue)
  271. );
  272. return $this;
  273. }
  274. public function gradient($startRed, $startGreen, $startBlue, $endRed, $endGreen, $endBlue, string $type): self
  275. {
  276. $type = strtoupper($type);
  277. $this->gradient = new Gradient(
  278. $this->createColor($startRed, $startGreen, $startBlue),
  279. $this->createColor($endRed, $endGreen, $endBlue),
  280. GradientType::$type()
  281. );
  282. return $this;
  283. }
  284. /**
  285. * Sets the eye style.
  286. *
  287. * @param string $style
  288. * @return Generator
  289. * @throws InvalidArgumentException
  290. */
  291. public function eye(string $style): self
  292. {
  293. if (! in_array($style, ['square', 'circle'])) {
  294. throw new InvalidArgumentException("\$style must be square or circle. {$style} is not a valid eye style.");
  295. }
  296. $this->eyeStyle = $style;
  297. return $this;
  298. }
  299. /**
  300. * Sets the style of the blocks for the QrCode.
  301. *
  302. * @param string $style
  303. * @param float $size
  304. * @return Generator
  305. * @throws InvalidArgumentException
  306. */
  307. public function style(string $style, float $size = 0.5): self
  308. {
  309. if (! in_array($style, ['square', 'dot', 'round'])) {
  310. throw new InvalidArgumentException("\$style must be square, dot, or round. {$style} is not a valid.");
  311. }
  312. if ($size < 0 || $size >= 1) {
  313. throw new InvalidArgumentException("\$size must be between 0 and 1. {$size} is not valid.");
  314. }
  315. $this->style = $style;
  316. $this->styleSize = $size;
  317. return $this;
  318. }
  319. /**
  320. * Sets the encoding for the QrCode.
  321. * Possible values are
  322. * ISO-8859-2, ISO-8859-3, ISO-8859-4, ISO-8859-5, ISO-8859-6,
  323. * ISO-8859-7, ISO-8859-8, ISO-8859-9, ISO-8859-10, ISO-8859-11,
  324. * ISO-8859-12, ISO-8859-13, ISO-8859-14, ISO-8859-15, ISO-8859-16,
  325. * SHIFT-JIS, WINDOWS-1250, WINDOWS-1251, WINDOWS-1252, WINDOWS-1256,
  326. * UTF-16BE, UTF-8, ASCII, GBK, EUC-KR.
  327. *
  328. * @param string $encoding
  329. * @return Generator
  330. */
  331. public function encoding(string $encoding): self
  332. {
  333. $this->encoding = strtoupper($encoding);
  334. return $this;
  335. }
  336. /**
  337. * Sets the error correction for the QrCode.
  338. * L: 7% loss.
  339. * M: 15% loss.
  340. * Q: 25% loss.
  341. * H: 30% loss.
  342. *
  343. * @param string $errorCorrection
  344. * @return Generator
  345. */
  346. public function errorCorrection(string $errorCorrection): self
  347. {
  348. $errorCorrection = strtoupper($errorCorrection);
  349. $this->errorCorrection = ErrorCorrectionLevel::$errorCorrection();
  350. return $this;
  351. }
  352. /**
  353. * Sets the margin of the QrCode.
  354. *
  355. * @param int $margin
  356. * @return Generator
  357. */
  358. public function margin(int $margin): self
  359. {
  360. $this->margin = $margin;
  361. return $this;
  362. }
  363. /**
  364. * Fetches the Writer.
  365. *
  366. * @param ImageRenderer $renderer
  367. * @return Writer
  368. */
  369. public function getWriter(ImageRenderer $renderer): Writer
  370. {
  371. return new Writer($renderer);
  372. }
  373. /**
  374. * Fetches the Image Renderer.
  375. *
  376. * @return ImageRenderer
  377. */
  378. public function getRenderer(): ImageRenderer
  379. {
  380. $renderer = new ImageRenderer(
  381. $this->getRendererStyle(),
  382. $this->getFormatter()
  383. );
  384. return $renderer;
  385. }
  386. /**
  387. * Returns the Renderer Style.
  388. *
  389. * @return RendererStyle
  390. */
  391. public function getRendererStyle(): RendererStyle
  392. {
  393. return new RendererStyle($this->pixels, $this->margin, $this->getModule(), $this->getEye(), $this->getFill());
  394. }
  395. /**
  396. * Fetches the formatter.
  397. *
  398. * @return ImageBackEndInterface
  399. */
  400. public function getFormatter(): ImageBackEndInterface
  401. {
  402. if ($this->format === 'png') {
  403. return new ImagickImageBackEnd('png');
  404. }
  405. if ($this->format === 'eps') {
  406. return new EpsImageBackEnd;
  407. }
  408. return new SvgImageBackEnd;
  409. }
  410. /**
  411. * Fetches the module.
  412. *
  413. * @return ModuleInterface
  414. */
  415. public function getModule(): ModuleInterface
  416. {
  417. if ($this->style === 'dot') {
  418. return new DotsModule($this->styleSize);
  419. }
  420. if ($this->style === 'round') {
  421. return new RoundnessModule($this->styleSize);
  422. }
  423. return SquareModule::instance();
  424. }
  425. /**
  426. * Fetches the eye style.
  427. *
  428. * @return EyeInterface
  429. */
  430. public function getEye(): EyeInterface
  431. {
  432. if ($this->eyeStyle === 'square') {
  433. return SquareEye::instance();
  434. }
  435. if ($this->eyeStyle === 'circle') {
  436. return SimpleCircleEye::instance();
  437. }
  438. return new ModuleEye($this->getModule());
  439. }
  440. /**
  441. * Fetches the color fill.
  442. *
  443. * @return Fill
  444. */
  445. public function getFill(): Fill
  446. {
  447. $foregroundColor = $this->color ?? new Rgb(0, 0, 0);
  448. $backgroundColor = $this->backgroundColor ?? new Rgb(255, 255, 255);
  449. $eye0 = $this->eyeColors[0] ?? EyeFill::inherit();
  450. $eye1 = $this->eyeColors[1] ?? EyeFill::inherit();
  451. $eye2 = $this->eyeColors[2] ?? EyeFill::inherit();
  452. if ($this->gradient) {
  453. return Fill::withForegroundGradient($backgroundColor, $this->gradient, $eye0, $eye1, $eye2);
  454. }
  455. return Fill::withForegroundColor($backgroundColor, $foregroundColor, $eye0, $eye1, $eye2);
  456. }
  457. /**
  458. * Creates a RGB or Alpha channel color.
  459. * @param int $red
  460. * @param int $green
  461. * @param int $blue
  462. * @param null|int $alpha
  463. * @return ColorInterface
  464. */
  465. public function createColor(int $red, int $green, int $blue, ?int $alpha = null): ColorInterface
  466. {
  467. if (is_null($alpha)) {
  468. return new Rgb($red, $green, $blue);
  469. }
  470. return new Alpha($alpha, new Rgb($red, $green, $blue));
  471. }
  472. /**
  473. * Creates a new DataType class dynamically.
  474. *
  475. * @param string $method
  476. * @return DataTypeInterface
  477. */
  478. protected function createClass(string $method): DataTypeInterface
  479. {
  480. $class = $this->formatClass($method);
  481. if (! class_exists($class)) {
  482. throw new BadMethodCallException();
  483. }
  484. return new $class();
  485. }
  486. /**
  487. * Formats the method name correctly.
  488. *
  489. * @param $method
  490. * @return string
  491. */
  492. protected function formatClass(string $method): string
  493. {
  494. $method = ucfirst($method);
  495. $class = "SimpleSoftwareIO\QrCode\DataTypes\\".$method;
  496. return $class;
  497. }
  498. }