QROptionsTrait.php 15 KB

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