QROptionsTrait.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <?php
  2. /**
  3. * Trait QROptionsTrait
  4. *
  5. * Note: the docblocks in this file are optimized for readability in PhpStorm ond on readthedocs.io
  6. *
  7. * @created 10.03.2018
  8. * @author smiley <smiley@chillerlan.net>
  9. * @copyright 2018 smiley
  10. * @license MIT
  11. *
  12. * @noinspection PhpUnused, PhpComposerExtensionStubsInspection
  13. */
  14. namespace chillerlan\QRCode;
  15. use chillerlan\QRCode\Common\{EccLevel, MaskPattern, Version};
  16. use chillerlan\QRCode\Output\QRMarkupSVG;
  17. use function constant, in_array, is_string, max, min, sprintf, strtolower, strtoupper, trim;
  18. use const JSON_THROW_ON_ERROR, JSON_UNESCAPED_SLASHES, PHP_EOL;
  19. /**
  20. * The QRCode plug-in settings & setter functionality
  21. */
  22. trait QROptionsTrait{
  23. /*
  24. * QR Code specific settings
  25. */
  26. /**
  27. * QR Code version number
  28. *
  29. * `1 ... 40` or `Version::AUTO` (default)
  30. *
  31. * @see \chillerlan\QRCode\Common\Version
  32. */
  33. protected int $version = Version::AUTO;
  34. /**
  35. * Minimum QR version
  36. *
  37. * if `QROptions::$version` is set to `Version::AUTO` (default: 1)
  38. */
  39. protected int $versionMin = 1;
  40. /**
  41. * Maximum QR version
  42. *
  43. * if `QROptions::$version` is set to `Version::AUTO` (default: 40)
  44. */
  45. protected int $versionMax = 40;
  46. /**
  47. * Error correct level
  48. *
  49. * the constant `EccLevel::X` where `X` is:
  50. *
  51. * - `L` => 7% (default)
  52. * - `M` => 15%
  53. * - `Q` => 25%
  54. * - `H` => 30%
  55. *
  56. * alternatively you can just pass the letters L/M/Q/H (case-insensitive) to the magic setter
  57. *
  58. * @see \chillerlan\QRCode\Common\EccLevel
  59. * @see https://github.com/chillerlan/php-qrcode/discussions/160
  60. */
  61. protected int $eccLevel = EccLevel::L;
  62. /**
  63. * Mask Pattern to use (no value in using, mostly for unit testing purposes)
  64. *
  65. * `0 ... 7` or `MaskPattern::PATTERN_AUTO` (default)
  66. *
  67. * @see \chillerlan\QRCode\Common\MaskPattern
  68. */
  69. protected int $maskPattern = MaskPattern::AUTO;
  70. /**
  71. * Add a "quiet zone" (margin) according to the QR code spec
  72. *
  73. * @see https://www.qrcode.com/en/howto/code.html
  74. */
  75. protected bool $addQuietzone = true;
  76. /**
  77. * Size of the quiet zone
  78. *
  79. * internally clamped to `0 ... $moduleCount / 2` (default: 4)
  80. */
  81. protected int $quietzoneSize = 4;
  82. /*
  83. * General output settings
  84. */
  85. /**
  86. * The FQCN of the `QROutputInterface` to use
  87. */
  88. protected string $outputInterface = QRMarkupSVG::class;
  89. /**
  90. * Return the image resource instead of a render if applicable.
  91. *
  92. * - `QRGdImage`: `resource` (PHP < 8), `GdImage`
  93. * - `QRImagick`: `Imagick`
  94. * - `QRFpdf`: `FPDF`
  95. *
  96. * This option overrides/ignores other output settings, such as `QROptions::$cachefile`
  97. * and `QROptions::$outputBase64`. (default: `false`)
  98. *
  99. * @see \chillerlan\QRCode\Output\QROutputInterface::dump()
  100. */
  101. protected bool $returnResource = false;
  102. /**
  103. * Optional cache file path `/path/to/cache.file`
  104. *
  105. * Please note that the `$file` parameter in `QRCode::render()` and `QRCode::renderMatrix()`
  106. * takes precedence over the `QROptions::$cachefile` value. (default: `null`)
  107. *
  108. * @see \chillerlan\QRCode\QRCode::render()
  109. * @see \chillerlan\QRCode\QRCode::renderMatrix()
  110. */
  111. protected string|null $cachefile = null;
  112. /**
  113. * Toggle base64 data URI or raw data output (if applicable)
  114. *
  115. * (default: `true`)
  116. *
  117. * @see \chillerlan\QRCode\Output\QROutputAbstract::toBase64DataURI()
  118. */
  119. protected bool $outputBase64 = true;
  120. /**
  121. * Newline string
  122. *
  123. * (default: `PHP_EOL`)
  124. */
  125. protected string $eol = PHP_EOL;
  126. /*
  127. * Common visual modifications
  128. */
  129. /**
  130. * Sets the image background color (if applicable)
  131. *
  132. * - `QRImagick`: defaults to `"white"`
  133. * - `QRGdImage`: defaults to `[255, 255, 255]`
  134. * - `QRFpdf`: defaults to blank internally (white page)
  135. */
  136. protected mixed $bgColor = null;
  137. /**
  138. * Whether to invert the matrix (reflectance reversal)
  139. *
  140. * (default: `false`)
  141. *
  142. * @see \chillerlan\QRCode\Data\QRMatrix::invert()
  143. */
  144. protected bool $invertMatrix = false;
  145. /**
  146. * Whether to draw the light (false) modules
  147. *
  148. * (default: `true`)
  149. */
  150. protected bool $drawLightModules = true;
  151. /**
  152. * Specify whether to draw the modules as filled circles
  153. *
  154. * a note for `GdImage` output:
  155. *
  156. * if `QROptions::$scale` is less than 20, the image will be upscaled internally, then the modules will be drawn
  157. * using `imagefilledellipse()` and then scaled back to the expected size
  158. *
  159. * No effect in: `QREps`, `QRFpdf`, `QRMarkupHTML`
  160. *
  161. * @see \imagefilledellipse()
  162. * @see https://github.com/chillerlan/php-qrcode/issues/23
  163. * @see https://github.com/chillerlan/php-qrcode/discussions/122
  164. */
  165. protected bool $drawCircularModules = false;
  166. /**
  167. * Specifies the radius of the modules when `QROptions::$drawCircularModules` is set to `true`
  168. *
  169. * (default: 0.45)
  170. */
  171. protected float $circleRadius = 0.45;
  172. /**
  173. * Specifies which module types to exclude when `QROptions::$drawCircularModules` is set to `true`
  174. *
  175. * (default: `[]`)
  176. */
  177. protected array $keepAsSquare = [];
  178. /**
  179. * Whether to connect the paths for the several module types to avoid weird glitches when using gradients etc.
  180. *
  181. * This option is exclusive to output classes that use the module collector `QROutputAbstract::collectModules()`,
  182. * which converts the `$M_TYPE` of all modules to `QRMatrix::M_DATA` and `QRMatrix::M_DATA_DARK` respectively.
  183. *
  184. * Module types that should not be added to the connected path can be excluded via `QROptions::$excludeFromConnect`.
  185. *
  186. * Currentty used in `QREps` and `QRMarkupSVG`.
  187. *
  188. * @see \chillerlan\QRCode\Output\QROutputAbstract::collectModules()
  189. * @see \chillerlan\QRCode\QROptionsTrait::$excludeFromConnect
  190. * @see https://github.com/chillerlan/php-qrcode/issues/57
  191. */
  192. protected bool $connectPaths = false;
  193. /**
  194. * Specify which paths/patterns to exclude from connecting if `QROptions::$connectPaths` is set to `true`
  195. *
  196. * @see \chillerlan\QRCode\QROptionsTrait::$connectPaths
  197. */
  198. protected array $excludeFromConnect = [];
  199. /**
  200. * Module values map
  201. *
  202. * - `QRImagick`, `QRMarkupHTML`, `QRMarkupSVG`: #ABCDEF, cssname, rgb(), rgba()...
  203. * - `QREps`, `QRFpdf`, `QRGdImage`: `[R, G, B]` // 0-255
  204. * - `QREps`: `[C, M, Y, K]` // 0-255
  205. *
  206. * @see \chillerlan\QRCode\Output\QROutputAbstract::setModuleValues()
  207. */
  208. protected array $moduleValues = [];
  209. /**
  210. * Toggles logo space creation
  211. *
  212. * @see \chillerlan\QRCode\QRCode::addMatrixModifications()
  213. * @see \chillerlan\QRCode\Data\QRMatrix::setLogoSpace()
  214. */
  215. protected bool $addLogoSpace = false;
  216. /**
  217. * Width of the logo space
  218. *
  219. * if only `QROptions::$logoSpaceWidth` is given, the logo space is assumed a square of that size
  220. */
  221. protected int|null $logoSpaceWidth = null;
  222. /**
  223. * Height of the logo space
  224. *
  225. * if only `QROptions::$logoSpaceHeight` is given, the logo space is assumed a square of that size
  226. */
  227. protected int|null $logoSpaceHeight = null;
  228. /**
  229. * Optional horizontal start position of the logo space (top left corner)
  230. */
  231. protected int|null $logoSpaceStartX = null;
  232. /**
  233. * Optional vertical start position of the logo space (top left corner)
  234. */
  235. protected int|null $logoSpaceStartY = null;
  236. /*
  237. * Common raster image settings (QRGdImage, QRImagick)
  238. */
  239. /**
  240. * Pixel size of a QR code module
  241. */
  242. protected int $scale = 5;
  243. /**
  244. * Toggle transparency
  245. *
  246. * - `QRGdImage` and `QRImagick`: the given `QROptions::$transparencyColor` is set as transparent
  247. *
  248. * @see https://github.com/chillerlan/php-qrcode/discussions/121
  249. */
  250. protected bool $imageTransparent = false;
  251. /**
  252. * Sets a transparency color for when `QROptions::$imageTransparent` is set to `true`.
  253. *
  254. * Defaults to `QROptions::$bgColor`.
  255. *
  256. * - `QRGdImage`: `[R, G, B]`, this color is set as transparent in `imagecolortransparent()`
  257. * - `QRImagick`: `"color_str"`, this color is set in `Imagick::transparentPaintImage()`
  258. *
  259. * @see \imagecolortransparent()
  260. * @see \Imagick::transparentPaintImage()
  261. *
  262. * @var mixed|null
  263. */
  264. protected mixed $transparencyColor = null;
  265. /**
  266. * Compression quality
  267. *
  268. * The given value depends on the used output type:
  269. *
  270. * - `QRGdImageBMP`: `[0...1]`
  271. * - `QRGdImageJPEG`: `[0...100]`
  272. * - `QRGdImageWEBP`: `[0...9]`
  273. * - `QRGdImagePNG`: `[0...100]`
  274. * - `QRImagick`: `[0...100]`
  275. *
  276. * @see \imagebmp()
  277. * @see \imagejpeg()
  278. * @see \imagepng()
  279. * @see \imagewebp()
  280. * @see \Imagick::setImageCompressionQuality()
  281. */
  282. protected int $quality = -1;
  283. /*
  284. * QRGdImage settings
  285. */
  286. /**
  287. * Toggles the usage of internal upscaling when `QROptions::$drawCircularModules` is set to `true` and
  288. * `QROptions::$scale` is less than 20
  289. *
  290. * @see \chillerlan\QRCode\Output\QRGdImage::createImage()
  291. * @see https://github.com/chillerlan/php-qrcode/issues/23
  292. */
  293. protected bool $gdImageUseUpscale = true;
  294. /*
  295. * QRImagick settings
  296. */
  297. /**
  298. * Imagick output format
  299. *
  300. * @see \Imagick::setImageFormat()
  301. * @see https://www.imagemagick.org/script/formats.php
  302. */
  303. protected string $imagickFormat = 'png32';
  304. /*
  305. * Common markup output settings (QRMarkupSVG, QRMarkupHTML)
  306. */
  307. /**
  308. * A common css class
  309. */
  310. protected string $cssClass = 'qrcode';
  311. /*
  312. * QRMarkupSVG settings
  313. */
  314. /**
  315. * Whether to add an XML header line or not, e.g. to embed the SVG directly in HTML
  316. *
  317. * `<?xml version="1.0" encoding="UTF-8"?>`
  318. */
  319. protected bool $svgAddXmlHeader = true;
  320. /**
  321. * Anything in the SVG `<defs>` tag
  322. *
  323. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
  324. */
  325. protected string $svgDefs = '';
  326. /**
  327. * Sets the value for the "preserveAspectRatio" on the `<svg>` element
  328. *
  329. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio
  330. */
  331. protected string $svgPreserveAspectRatio = 'xMidYMid';
  332. /**
  333. * Whether to use the SVG `fill` attributes
  334. *
  335. * If set to `true` (default), the `fill` attribute will be set with the module value for the `<path>` element's `$M_TYPE`.
  336. * When set to `false`, the module values map will be ignored and the QR Code may be styled via CSS.
  337. *
  338. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill
  339. */
  340. protected bool $svgUseFillAttributes = true;
  341. /*
  342. * QRStringText settings
  343. */
  344. /**
  345. * An optional line prefix, e.g. empty space to align the QR Code in a console
  346. */
  347. protected string $textLineStart = '';
  348. /*
  349. * QRStringJSON settings
  350. */
  351. /**
  352. * Sets the flags to use for the `json_encode()` call
  353. *
  354. * @see https://www.php.net/manual/json.constants.php
  355. */
  356. protected int $jsonFlags = JSON_THROW_ON_ERROR|JSON_UNESCAPED_SLASHES;
  357. /*
  358. * QRFpdf settings
  359. */
  360. /**
  361. * Measurement unit for `FPDF` output: `pt`, `mm`, `cm`, `in` (default: `pt`)
  362. *
  363. * @see FPDF::__construct()
  364. */
  365. protected string $fpdfMeasureUnit = 'pt';
  366. /*
  367. * QRMarkupXML settings
  368. */
  369. /**
  370. * Sets an optional XSLT stylesheet in the XML output
  371. *
  372. * @see https://developer.mozilla.org/en-US/docs/Web/XSLT
  373. */
  374. protected string|null $xmlStylesheet = null;
  375. /**
  376. * clamp min/max version number
  377. */
  378. protected function setMinMaxVersion(int $versionMin, int $versionMax):void{
  379. $min = max(1, min(40, $versionMin));
  380. $max = max(1, min(40, $versionMax));
  381. $this->versionMin = min($min, $max);
  382. $this->versionMax = max($min, $max);
  383. }
  384. /**
  385. * sets the minimum version number
  386. */
  387. protected function set_versionMin(int $version):void{
  388. $this->setMinMaxVersion($version, $this->versionMax);
  389. }
  390. /**
  391. * sets the maximum version number
  392. */
  393. protected function set_versionMax(int $version):void{
  394. $this->setMinMaxVersion($this->versionMin, $version);
  395. }
  396. /**
  397. * sets/clamps the version number
  398. */
  399. protected function set_version(int $version):void{
  400. $this->version = ($version !== Version::AUTO) ? max(1, min(40, $version)) : Version::AUTO;
  401. }
  402. /**
  403. * sets the ECC level
  404. *
  405. * @throws \chillerlan\QRCode\QRCodeException
  406. */
  407. protected function set_eccLevel(int|string $eccLevel):void{
  408. if(is_string($eccLevel)){
  409. $ecc = strtoupper(trim($eccLevel));
  410. if(!in_array($ecc, ['L', 'M', 'Q', 'H'])){
  411. throw new QRCodeException(sprintf('Invalid ECC level: "%s"', $ecc));
  412. }
  413. // @todo: PHP 8.3+
  414. // $eccLevel = EccLevel::{$ecc};
  415. $eccLevel = constant(EccLevel::class.'::'.$ecc);
  416. }
  417. if((0b11 & $eccLevel) !== $eccLevel){
  418. throw new QRCodeException(sprintf('Invalid ECC level: "%s"', $eccLevel));
  419. }
  420. $this->eccLevel = $eccLevel;
  421. }
  422. /**
  423. * sets/clamps the quiet zone size
  424. */
  425. protected function set_quietzoneSize(int $quietzoneSize):void{
  426. $this->quietzoneSize = max(0, min($quietzoneSize, 75));
  427. }
  428. /**
  429. * sets the FPDF measurement unit
  430. *
  431. * @codeCoverageIgnore
  432. */
  433. protected function set_fpdfMeasureUnit(string $unit):void{
  434. $unit = strtolower($unit);
  435. if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){
  436. $this->fpdfMeasureUnit = $unit;
  437. }
  438. // @todo throw or ignore silently?
  439. }
  440. /**
  441. * clamp the logo space values between 0 and maximum length (177 modules at version 40)
  442. */
  443. protected function clampLogoSpaceValue(int|null $value):int|null{
  444. if($value === null){
  445. return null;
  446. }
  447. return (int)max(0, min(177, $value));
  448. }
  449. /**
  450. * clamp/set logo space width
  451. */
  452. protected function set_logoSpaceWidth(int|null $value):void{
  453. $this->logoSpaceWidth = $this->clampLogoSpaceValue($value);
  454. }
  455. /**
  456. * clamp/set logo space height
  457. */
  458. protected function set_logoSpaceHeight(int|null $value):void{
  459. $this->logoSpaceHeight = $this->clampLogoSpaceValue($value);
  460. }
  461. /**
  462. * clamp/set horizontal logo space start
  463. */
  464. protected function set_logoSpaceStartX(int|null $value):void{
  465. $this->logoSpaceStartX = $this->clampLogoSpaceValue($value);
  466. }
  467. /**
  468. * clamp/set vertical logo space start
  469. */
  470. protected function set_logoSpaceStartY(int|null $value):void{
  471. $this->logoSpaceStartY = $this->clampLogoSpaceValue($value);
  472. }
  473. /**
  474. * clamp/set SVG circle radius
  475. */
  476. protected function set_circleRadius(float $circleRadius):void{
  477. $this->circleRadius = max(0.1, min(0.75, $circleRadius));
  478. }
  479. }