QROptionsTrait.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. <?php
  2. /**
  3. * Trait QROptionsTrait
  4. *
  5. * @created 10.03.2018
  6. * @author smiley <smiley@chillerlan.net>
  7. * @copyright 2018 smiley
  8. * @license MIT
  9. *
  10. * @noinspection PhpUnused
  11. */
  12. namespace chillerlan\QRCode;
  13. use chillerlan\QRCode\Common\EccLevel;
  14. use chillerlan\QRCode\Decoder\{GDLuminanceSource, IMagickLuminanceSource};
  15. use function array_values, count, extension_loaded, in_array, is_numeric, max, min, sprintf, strtolower;
  16. /**
  17. * The QRCode plug-in settings & setter functionality
  18. */
  19. trait QROptionsTrait{
  20. /**
  21. * QR Code version number
  22. *
  23. * [1 ... 40] or QRCode::VERSION_AUTO
  24. */
  25. protected int $version = QRCode::VERSION_AUTO;
  26. /**
  27. * Minimum QR version
  28. *
  29. * if $version = QRCode::VERSION_AUTO
  30. */
  31. protected int $versionMin = 1;
  32. /**
  33. * Maximum QR version
  34. */
  35. protected int $versionMax = 40;
  36. /**
  37. * Error correct level
  38. *
  39. * QRCode::ECC_X where X is:
  40. *
  41. * - L => 7%
  42. * - M => 15%
  43. * - Q => 25%
  44. * - H => 30%
  45. */
  46. protected int $eccLevel = EccLevel::L;
  47. /**
  48. * Mask Pattern to use (no value in using, mostly for unit testing purposes)
  49. *
  50. * [0...7] or QRCode::MASK_PATTERN_AUTO
  51. */
  52. protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
  53. /**
  54. * Add a "quiet zone" (margin) according to the QR code spec
  55. */
  56. protected bool $addQuietzone = true;
  57. /**
  58. * Size of the quiet zone
  59. *
  60. * internally clamped to [0 ... $moduleCount / 2], defaults to 4 modules
  61. */
  62. protected int $quietzoneSize = 4;
  63. /**
  64. * The output type
  65. *
  66. * - QRCode::OUTPUT_MARKUP_XXXX where XXXX = HTML, SVG
  67. * - QRCode::OUTPUT_IMAGE_XXX where XXX = PNG, GIF, JPG
  68. * - QRCode::OUTPUT_STRING_XXXX where XXXX = TEXT, JSON
  69. * - QRCode::OUTPUT_CUSTOM
  70. */
  71. protected string $outputType = QRCode::OUTPUT_MARKUP_SVG;
  72. /**
  73. * the FQCN of the custom QROutputInterface if $outputType is set to QRCode::OUTPUT_CUSTOM
  74. */
  75. protected ?string $outputInterface = null;
  76. /**
  77. * /path/to/cache.file
  78. */
  79. protected ?string $cachefile = null;
  80. /**
  81. * newline string [HTML, SVG, TEXT]
  82. */
  83. protected string $eol = PHP_EOL;
  84. /**
  85. * size of a QR code pixel [SVG, IMAGE_*], HTML via CSS
  86. */
  87. protected int $scale = 5;
  88. /**
  89. * a common css class
  90. */
  91. protected string $cssClass = 'qrcode';
  92. /**
  93. * SVG opacity
  94. */
  95. protected float $svgOpacity = 1.0;
  96. /**
  97. * anything between <defs>
  98. *
  99. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/defs
  100. */
  101. protected string $svgDefs = '';
  102. /**
  103. * SVG viewBox size. a single integer number which defines width/height of the viewBox attribute.
  104. *
  105. * viewBox="0 0 x x"
  106. *
  107. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
  108. * @see https://css-tricks.com/scale-svg/#article-header-id-3
  109. */
  110. protected ?int $svgViewBoxSize = null;
  111. /**
  112. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/preserveAspectRatio
  113. */
  114. protected string $svgPreserveAspectRatio = 'xMidYMid';
  115. /**
  116. * optional "width" attribute with the specified value (note that the value is not checked!)
  117. *
  118. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/width
  119. */
  120. protected ?string $svgWidth = null;
  121. /**
  122. * optional "height" attribute with the specified value (note that the value is not checked!)
  123. *
  124. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/height
  125. */
  126. protected ?string $svgHeight = null;
  127. /**
  128. * whether to connect the paths for the several module types to avoid weird glitches when using gradients etc.
  129. *
  130. * @see https://github.com/chillerlan/php-qrcode/issues/57
  131. */
  132. protected bool $svgConnectPaths = false;
  133. /**
  134. * specify which paths/patterns to exclude from connecting if $svgConnectPaths is set to true
  135. */
  136. protected array $svgExcludeFromConnect = [];
  137. /**
  138. * specify whether to draw the modules as filled circles
  139. *
  140. * @see https://github.com/chillerlan/php-qrcode/issues/23
  141. */
  142. protected bool $drawCircularModules = false;
  143. /**
  144. * specifies the radius of the modules when $svgDrawCircularModules is set to true
  145. */
  146. protected float $circleRadius = 0.45;
  147. /**
  148. * specifies which module types to exclude when $svgDrawCircularModules is set to true
  149. */
  150. protected array $keepAsSquare = [];
  151. /**
  152. * string substitute for dark
  153. */
  154. protected string $textDark = '🔴';
  155. /**
  156. * string substitute for light
  157. */
  158. protected string $textLight = '⭕';
  159. /**
  160. * markup substitute for dark (CSS value)
  161. */
  162. protected string $markupDark = '#000';
  163. /**
  164. * markup substitute for light (CSS value)
  165. */
  166. protected string $markupLight = '#fff';
  167. /**
  168. * Return the image resource instead of a render if applicable.
  169. * This option overrides other output options, such as $cachefile and $imageBase64.
  170. *
  171. * Supported by the following modules:
  172. *
  173. * - QRImage: resource (PHP < 8), GdImage
  174. * - QRImagick: Imagick
  175. * - QRFpdf: FPDF
  176. *
  177. * @see \chillerlan\QRCode\Output\QROutputInterface::dump()
  178. *
  179. * @var bool
  180. */
  181. protected bool $returnResource = false;
  182. /**
  183. * toggle base64 or raw image data
  184. */
  185. protected bool $imageBase64 = true;
  186. /**
  187. * toggle transparency, not supported by jpg
  188. */
  189. protected bool $imageTransparent = true;
  190. /**
  191. * @see imagecolortransparent()
  192. *
  193. * [R, G, B]
  194. */
  195. protected array $imageTransparencyBG = [255, 255, 255];
  196. /**
  197. * @see imagepng()
  198. */
  199. protected int $pngCompression = -1;
  200. /**
  201. * @see imagejpeg()
  202. */
  203. protected int $jpegQuality = 85;
  204. /**
  205. * Imagick output format
  206. *
  207. * @see \Imagick::setImageFormat()
  208. * @see https://www.imagemagick.org/script/formats.php
  209. */
  210. protected string $imagickFormat = 'png32';
  211. /**
  212. * Imagick background color (defaults to "transparent")
  213. *
  214. * @see \ImagickPixel::__construct()
  215. */
  216. protected ?string $imagickBG = null;
  217. /**
  218. * Measurement unit for FPDF output: pt, mm, cm, in (defaults to "pt")
  219. *
  220. * @see \FPDF::__construct()
  221. */
  222. protected string $fpdfMeasureUnit = 'pt';
  223. /**
  224. * Module values map
  225. *
  226. * - HTML, IMAGICK: #ABCDEF, cssname, rgb(), rgba()...
  227. * - IMAGE: [63, 127, 255] // R, G, B
  228. */
  229. protected ?array $moduleValues = null;
  230. /**
  231. * use Imaagick (if available) when reading QR Codes
  232. */
  233. protected bool $readerUseImagickIfAvailable = false;
  234. /**
  235. * grayscale the image before reading
  236. */
  237. protected bool $readerGrayscale = false;
  238. /**
  239. * increase the contrast before reading
  240. *
  241. * note that applying contrast works different in GD and Imagick, so mileage may vary
  242. */
  243. protected bool $readerIncreaseContrast = false;
  244. /**
  245. * Toggles logo space creation
  246. */
  247. protected bool $addLogoSpace = false;
  248. /**
  249. * width of the logo space
  250. */
  251. protected int $logoSpaceWidth = 0;
  252. /**
  253. * height of the logo space
  254. */
  255. protected int $logoSpaceHeight = 0;
  256. /**
  257. * optional horizontal start position of the logo space (top left corner)
  258. */
  259. protected ?int $logoSpaceStartX = null;
  260. /**
  261. * optional vertical start position of the logo space (top left corner)
  262. */
  263. protected ?int $logoSpaceStartY = null;
  264. /**
  265. * clamp min/max version number
  266. */
  267. protected function setMinMaxVersion(int $versionMin, int $versionMax):void{
  268. $min = max(1, min(40, $versionMin));
  269. $max = max(1, min(40, $versionMax));
  270. $this->versionMin = min($min, $max);
  271. $this->versionMax = max($min, $max);
  272. }
  273. /**
  274. * sets the minimum version number
  275. */
  276. protected function set_versionMin(int $version):void{
  277. $this->setMinMaxVersion($version, $this->versionMax);
  278. }
  279. /**
  280. * sets the maximum version number
  281. */
  282. protected function set_versionMax(int $version):void{
  283. $this->setMinMaxVersion($this->versionMin, $version);
  284. }
  285. /**
  286. * sets the error correction level
  287. *
  288. * @throws \chillerlan\QRCode\QRCodeException
  289. */
  290. protected function set_eccLevel(int $eccLevel):void{
  291. if(!in_array($eccLevel, [EccLevel::L, EccLevel::M, EccLevel::Q, EccLevel::H], true)){
  292. throw new QRCodeException(sprintf('Invalid error correct level: %s', $eccLevel));
  293. }
  294. $this->eccLevel = $eccLevel;
  295. }
  296. /**
  297. * sets/clamps the mask pattern
  298. */
  299. protected function set_maskPattern(int $maskPattern):void{
  300. if($maskPattern !== QRCode::MASK_PATTERN_AUTO){
  301. $this->maskPattern = max(0, min(7, $maskPattern));
  302. }
  303. }
  304. /**
  305. * sets the transparency background color
  306. *
  307. * @throws \chillerlan\QRCode\QRCodeException
  308. */
  309. protected function set_imageTransparencyBG(array $imageTransparencyBG):void{
  310. // invalid value - set to white as default
  311. if(count($imageTransparencyBG) < 3){
  312. $this->imageTransparencyBG = [255, 255, 255];
  313. return;
  314. }
  315. foreach($imageTransparencyBG as $k => $v){
  316. // cut off exceeding items
  317. if($k > 2){
  318. break;
  319. }
  320. if(!is_numeric($v)){
  321. throw new QRCodeException('Invalid RGB value.');
  322. }
  323. // clamp the values
  324. $this->imageTransparencyBG[$k] = max(0, min(255, (int)$v));
  325. }
  326. // use the array values to not run into errors with the spread operator (...$arr)
  327. $this->imageTransparencyBG = array_values($this->imageTransparencyBG);
  328. }
  329. /**
  330. * sets/clamps the version number
  331. */
  332. protected function set_version(int $version):void{
  333. if($version !== QRCode::VERSION_AUTO){
  334. $this->version = max(1, min(40, $version));
  335. }
  336. }
  337. /**
  338. * sets the FPDF measurement unit
  339. *
  340. * @codeCoverageIgnore
  341. */
  342. protected function set_fpdfMeasureUnit(string $unit):void{
  343. $unit = strtolower($unit);
  344. if(in_array($unit, ['cm', 'in', 'mm', 'pt'], true)){
  345. $this->fpdfMeasureUnit = $unit;
  346. }
  347. // @todo throw or ignore silently?
  348. }
  349. /**
  350. * enables Imagick for the QR Code reader if the extension is available
  351. */
  352. protected function set_readerUseImagickIfAvailable(bool $useImagickIfAvailable):void{
  353. $this->readerUseImagickIfAvailable = $useImagickIfAvailable && extension_loaded('imagick');
  354. }
  355. /**
  356. * returns the FQCN of the luminance source class to use in the reader (GD or Imagick)
  357. *
  358. * @see \chillerlan\QRCode\Decoder\LuminanceSourceInterface
  359. */
  360. public function getLuminanceSourceFQCN():string{
  361. // i still hate this
  362. return $this->readerUseImagickIfAvailable
  363. ? IMagickLuminanceSource::class
  364. : GDLuminanceSource::class;
  365. }
  366. /**
  367. * clamp the logo space values between 0 and maximum length (177 modules at version 40)
  368. */
  369. protected function clampLogoSpaceValue(int $value):int{
  370. return (int)max(0, min(177, $value));
  371. }
  372. /**
  373. * clamp/set logo space width
  374. */
  375. protected function set_logoSpaceWidth(int $value):void{
  376. $this->logoSpaceWidth = $this->clampLogoSpaceValue($value);
  377. }
  378. /**
  379. * clamp/set logo space height
  380. */
  381. protected function set_logoSpaceHeight(int $value):void{
  382. $this->logoSpaceHeight = $this->clampLogoSpaceValue($value);
  383. }
  384. /**
  385. * clamp/set horizontal logo space start
  386. */
  387. protected function set_logoSpaceStartX(?int $value):void{
  388. $this->logoSpaceStartX = $value === null ? null : $this->clampLogoSpaceValue($value);
  389. }
  390. /**
  391. * clamp/set vertical logo space start
  392. */
  393. protected function set_logoSpaceStartY(?int $value):void{
  394. $this->logoSpaceStartY = $value === null ? null : $this->clampLogoSpaceValue($value);
  395. }
  396. /**
  397. * clamp/set SVG circle radius
  398. */
  399. protected function set_circleRadius(float $circleRadius):void{
  400. $this->circleRadius = max(0.1, min(0.75, $circleRadius));
  401. }
  402. }