ソースを参照

Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop

Laurent Destailleur 2 年 前
コミット
57fd5b82ac
100 ファイル変更10075 行追加30 行削除
  1. 1 1
      htdocs/admin/pdf_other.php
  2. 20 14
      htdocs/core/modules/facture/doc/pdf_crabe.modules.php
  3. 22 15
      htdocs/core/modules/facture/doc/pdf_sponge.modules.php
  4. 151 0
      htdocs/core/modules/facture/modules_facture.php
  5. 22 0
      htdocs/includes/bacon/bacon-qr-code/LICENSE
  6. 39 0
      htdocs/includes/bacon/bacon-qr-code/README.md
  7. 44 0
      htdocs/includes/bacon/bacon-qr-code/composer.json.disabled
  8. 372 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/BitArray.php
  9. 313 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/BitMatrix.php
  10. 41 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/BitUtils.php
  11. 183 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/CharacterSetEci.php
  12. 49 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/EcBlock.php
  13. 74 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/EcBlocks.php
  14. 63 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.php
  15. 203 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/FormatInformation.php
  16. 79 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/Mode.php
  17. 468 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/ReedSolomonCodec.php
  18. 596 0
      htdocs/includes/bacon/bacon-qr-code/src/Common/Version.php
  19. 58 0
      htdocs/includes/bacon/bacon-qr-code/src/Encoder/BlockPair.php
  20. 150 0
      htdocs/includes/bacon/bacon-qr-code/src/Encoder/ByteMatrix.php
  21. 668 0
      htdocs/includes/bacon/bacon-qr-code/src/Encoder/Encoder.php
  22. 271 0
      htdocs/includes/bacon/bacon-qr-code/src/Encoder/MaskUtil.php
  23. 513 0
      htdocs/includes/bacon/bacon-qr-code/src/Encoder/MatrixUtil.php
  24. 141 0
      htdocs/includes/bacon/bacon-qr-code/src/Encoder/QrCode.php
  25. 10 0
      htdocs/includes/bacon/bacon-qr-code/src/Exception/ExceptionInterface.php
  26. 8 0
      htdocs/includes/bacon/bacon-qr-code/src/Exception/InvalidArgumentException.php
  27. 8 0
      htdocs/includes/bacon/bacon-qr-code/src/Exception/OutOfBoundsException.php
  28. 8 0
      htdocs/includes/bacon/bacon-qr-code/src/Exception/RuntimeException.php
  29. 8 0
      htdocs/includes/bacon/bacon-qr-code/src/Exception/UnexpectedValueException.php
  30. 8 0
      htdocs/includes/bacon/bacon-qr-code/src/Exception/WriterException.php
  31. 57 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Alpha.php
  32. 103 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.php
  33. 22 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.php
  34. 46 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Gray.php
  35. 88 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Rgb.php
  36. 38 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.php
  37. 26 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.php
  38. 54 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/ModuleEye.php
  39. 54 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php
  40. 53 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.php
  41. 376 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php
  42. 87 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php
  43. 336 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/ImagickImageBackEnd.php
  44. 369 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.php
  45. 68 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.php
  46. 152 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/ImageRenderer.php
  47. 63 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/DotsModule.php
  48. 100 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php
  49. 169 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php
  50. 18 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.php
  51. 129 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/RoundnessModule.php
  52. 47 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/SquareModule.php
  53. 29 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Close.php
  54. 92 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Curve.php
  55. 278 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.php
  56. 41 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Line.php
  57. 41 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Move.php
  58. 12 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/OperationInterface.php
  59. 106 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Path.php
  60. 86 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/PlainTextRenderer.php
  61. 11 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererInterface.php
  62. 74 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/EyeFill.php
  63. 168 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/Fill.php
  64. 46 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/Gradient.php
  65. 22 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/GradientType.php
  66. 90 0
      htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/RendererStyle.php
  67. 71 0
      htdocs/includes/bacon/bacon-qr-code/src/Writer.php
  68. 47 0
      htdocs/includes/dasprid/enum/.github/workflows/tests.yml
  69. 22 0
      htdocs/includes/dasprid/enum/LICENSE
  70. 164 0
      htdocs/includes/dasprid/enum/README.md
  71. 34 0
      htdocs/includes/dasprid/enum/composer.json.disabled
  72. 241 0
      htdocs/includes/dasprid/enum/src/AbstractEnum.php
  73. 385 0
      htdocs/includes/dasprid/enum/src/EnumMap.php
  74. 10 0
      htdocs/includes/dasprid/enum/src/Exception/CloneNotSupportedException.php
  75. 10 0
      htdocs/includes/dasprid/enum/src/Exception/ExceptionInterface.php
  76. 10 0
      htdocs/includes/dasprid/enum/src/Exception/ExpectationException.php
  77. 10 0
      htdocs/includes/dasprid/enum/src/Exception/IllegalArgumentException.php
  78. 10 0
      htdocs/includes/dasprid/enum/src/Exception/MismatchException.php
  79. 10 0
      htdocs/includes/dasprid/enum/src/Exception/SerializeNotSupportedException.php
  80. 10 0
      htdocs/includes/dasprid/enum/src/Exception/UnserializeNotSupportedException.php
  81. 55 0
      htdocs/includes/dasprid/enum/src/NullValue.php
  82. 2 0
      htdocs/includes/endroid/qr-code/.gitattributes
  83. 1 0
      htdocs/includes/endroid/qr-code/.github/FUNDING.yml
  84. 4 0
      htdocs/includes/endroid/qr-code/.gitignore
  85. 19 0
      htdocs/includes/endroid/qr-code/LICENSE
  86. 210 0
      htdocs/includes/endroid/qr-code/README.md
  87. BIN
      htdocs/includes/endroid/qr-code/assets/blackfire.png
  88. BIN
      htdocs/includes/endroid/qr-code/assets/noto_sans.otf
  89. BIN
      htdocs/includes/endroid/qr-code/assets/open_sans.ttf
  90. 57 0
      htdocs/includes/endroid/qr-code/composer.json.disabled
  91. 30 0
      htdocs/includes/endroid/qr-code/src/Bacon/ErrorCorrectionLevelConverter.php
  92. 32 0
      htdocs/includes/endroid/qr-code/src/Bacon/MatrixFactory.php
  93. 279 0
      htdocs/includes/endroid/qr-code/src/Builder/Builder.php
  94. 63 0
      htdocs/includes/endroid/qr-code/src/Builder/BuilderInterface.php
  95. 25 0
      htdocs/includes/endroid/qr-code/src/Builder/BuilderRegistry.php
  96. 12 0
      htdocs/includes/endroid/qr-code/src/Builder/BuilderRegistryInterface.php
  97. 56 0
      htdocs/includes/endroid/qr-code/src/Color/Color.php
  98. 23 0
      htdocs/includes/endroid/qr-code/src/Color/ColorInterface.php
  99. 21 0
      htdocs/includes/endroid/qr-code/src/Encoding/Encoding.php
  100. 10 0
      htdocs/includes/endroid/qr-code/src/Encoding/EncodingInterface.php

+ 1 - 1
htdocs/admin/pdf_other.php

@@ -168,7 +168,7 @@ if (isModEnabled('facture')) {
 	print '</td></tr>';
 
 	print '<tr class="oddeven"><td>';
-	print $form->textwithpicto($langs->trans("INVOICE_ADD_SWISS_QR_CODE"), '');
+	print $form->textwithpicto($langs->trans("INVOICE_ADD_SWISS_QR_CODE"), $langs->trans("INVOICE_ADD_SWISS_QR_CODEMore"));
 	print '</td><td>';
 	if ($conf->use_javascript_ajax) {
 		print ajax_constantonoff('INVOICE_ADD_SWISS_QR_CODE');

+ 20 - 14
htdocs/core/modules/facture/doc/pdf_crabe.modules.php

@@ -354,6 +354,12 @@ class pdf_crabe extends ModelePDFFactures
 					$heightforfooter += 6;
 				}
 
+				$heightforqrinvoice_firstpage = $this->getHeightForQRInvoice(1, $object, $langs);
+				if ($heightforqrinvoice_firstpage > 0) {
+					// Shrink infotot to a base 30
+					$heightforinfotot = 30 + (4 * $nbpayments); // Height reserved to output the info and total part and payment part
+				}
+
 				if (class_exists('TCPDF')) {
 					$pdf->setPrintHeader(false);
 					$pdf->setPrintFooter(false);
@@ -487,8 +493,6 @@ class pdf_crabe extends ModelePDFFactures
 				$qrcodestring = '';
 				if (!empty($conf->global->INVOICE_ADD_ZATCA_QR_CODE)) {
 					$qrcodestring = $object->buildZATCAQRString();
-				} elseif (!empty($conf->global->INVOICE_ADD_SWISS_QR_CODE)) {
-					$qrcodestring = $object->buildSwitzerlandQRString();
 				}
 				if ($qrcodestring) {
 					$qrcodecolor = array('25', '25', '25');
@@ -597,15 +601,16 @@ class pdf_crabe extends ModelePDFFactures
 					}
 
 					$pdf->setTopMargin($tab_top_newpage);
-					$pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext + $heightforinfotot); // The only function to edit the bottom margin of current page to set it.
 					$pageposbefore = $pdf->getPage();
+					$page_bottom_margin = $heightforfooter + $heightforfreetext + $heightforinfotot + $this->getHeightForQRInvoice($pageposbefore, $object, $langs);
+					$pdf->setPageOrientation('', 1, $page_bottom_margin);
 
 					$showpricebeforepagebreak = 1;
 					$posYAfterImage = 0;
 					$posYAfterDescription = 0;
 
 					// We start with Photo of product line
-					if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforinfotot))) {	// If photo too high, we moved completely on new page
+					if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - $page_bottom_margin)) {	// If photo too high, we moved completely on new page
 						$pdf->AddPage('', '', true);
 						if (!empty($tplidx)) {
 							$pdf->useTemplate($tplidx);
@@ -648,7 +653,7 @@ class pdf_crabe extends ModelePDFFactures
 						$pageposafter = $pdf->getPage();
 						$posyafter = $pdf->GetY();
 						//var_dump($posyafter); var_dump(($this->page_hauteur - ($heightforfooter+$heightforfreetext+$heightforinfotot))); exit;
-						if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforinfotot))) {	// There is no space left for total+free text
+						if ($posyafter > ($this->page_hauteur - $page_bottom_margin)) {	// There is no space left for total+free text
 							if ($i == ($nblines - 1)) {	// No more lines, and no space left to show total, so we create a new page
 								$pdf->AddPage('', '', true);
 								if (!empty($tplidx)) {
@@ -832,11 +837,11 @@ class pdf_crabe extends ModelePDFFactures
 					while ($pagenb < $pageposafter) {
 						$pdf->setPage($pagenb);
 						if ($pagenb == 1) {
-							$this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter, 0, $outputlangs, 0, 1, $object->multicurrency_code);
+							$this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 1, $object->multicurrency_code);
 						} else {
 							$this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code);
 						}
-						$this->_pagefoot($pdf, $object, $outputlangs, 1);
+						$this->_pagefoot($pdf, $object, $outputlangs, 1, $this->getHeightForQRInvoice($pagenb, $object, $langs));
 						$pagenb++;
 						$pdf->setPage($pagenb);
 						$pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it.
@@ -850,11 +855,11 @@ class pdf_crabe extends ModelePDFFactures
 					}
 					if (isset($object->lines[$i + 1]->pagebreak) && $object->lines[$i + 1]->pagebreak) {
 						if ($pagenb == 1) {
-							$this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter, 0, $outputlangs, 0, 1, $object->multicurrency_code);
+							$this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 1, $object->multicurrency_code);
 						} else {
 							$this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code);
 						}
-						$this->_pagefoot($pdf, $object, $outputlangs, 1);
+						$this->_pagefoot($pdf, $object, $outputlangs, 1, $this->getHeightForQRInvoice($pagenb, $object, $langs));
 						// New page
 						$pdf->AddPage();
 						if (!empty($tplidx)) {
@@ -870,8 +875,8 @@ class pdf_crabe extends ModelePDFFactures
 
 				// Show square
 				if ($pagenb == 1) {
-					$this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 0, 0, $object->multicurrency_code);
-					$bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1;
+					$this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforinfotot - $heightforfreetext - $heightforfooter - $heightforqrinvoice_firstpage, 0, $outputlangs, 0, 0, $object->multicurrency_code);
+					$bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter - $heightforqrinvoice_firstpage + 1;
 				} else {
 					$this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 1, 0, $object->multicurrency_code);
 					$bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1;
@@ -890,11 +895,12 @@ class pdf_crabe extends ModelePDFFactures
 				}
 
 				// Pagefoot
-				$this->_pagefoot($pdf, $object, $outputlangs);
+				$this->_pagefoot($pdf, $object, $outputlangs, 0, $this->getHeightForQRInvoice($pageposbefore, $object, $langs));
 				if (method_exists($pdf, 'AliasNbPages')) {
 					$pdf->AliasNbPages();
 				}
 
+				$this->addSwissQRInvoice($pdf, $object, $outputlangs);
 				$pdf->Close();
 
 				$pdf->Output($file, 'F');
@@ -2192,9 +2198,9 @@ class pdf_crabe extends ModelePDFFactures
 	 *      @param	int			$hidefreetext		1=Hide free text
 	 *      @return	int								Return height of bottom margin including footer text
 	 */
-	protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0)
+	protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0, $heightforqrinvoice = 0)
 	{
 		$showdetails = getDolGlobalInt('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS', 0);
-		return pdf_pagefoot($pdf, $outputlangs, 'INVOICE_FREE_TEXT', $this->emetteur, $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark);
+		return pdf_pagefoot($pdf, $outputlangs, 'INVOICE_FREE_TEXT', $this->emetteur, $heightforqrinvoice + $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark);
 	}
 }

+ 22 - 15
htdocs/core/modules/facture/doc/pdf_sponge.modules.php

@@ -382,6 +382,10 @@ class pdf_sponge extends ModelePDFFactures
 				$this->heightforfreetext = (isset($conf->global->MAIN_PDF_FREETEXT_HEIGHT) ? $conf->global->MAIN_PDF_FREETEXT_HEIGHT : 5); // Height reserved to output the free text on last page
 				$this->heightforfooter = $this->marge_basse + (empty($conf->global->MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS) ? 12 : 22); // Height reserved to output the footer (value include bottom margin)
 
+				if ($this->getHeightForQRInvoice(1, $object, $langs) > 0) {
+					// Shrink infotot to a base 30
+					$this->heightforinfotot = 30 + (4 * $nbpayments); // Height reserved to output the info and total part and payment part
+				}
 				if (class_exists('TCPDF')) {
 					$pdf->setPrintHeader(false);
 					$pdf->setPrintFooter(false);
@@ -498,8 +502,6 @@ class pdf_sponge extends ModelePDFFactures
 				$qrcodestring = '';
 				if (!empty($conf->global->INVOICE_ADD_ZATCA_QR_CODE)) {
 					$qrcodestring = $object->buildZATCAQRString();
-				} elseif (!empty($conf->global->INVOICE_ADD_SWISS_QR_CODE)) {
-					$qrcodestring = $object->buildSwitzerlandQRString();
 				}
 				if ($qrcodestring) {
 					$qrcodecolor = array('25', '25', '25');
@@ -534,7 +536,7 @@ class pdf_sponge extends ModelePDFFactures
 
 
 				// Define heigth of table for lines (for first page)
-				$tab_height = $this->page_hauteur - $this->tab_top - $this->heightforfooter - $this->heightforfreetext;
+				$tab_height = $this->page_hauteur - $this->tab_top - $this->heightforfooter - $this->heightforfreetext - $this->getHeightForQRInvoice(1, $object, $langs);
 
 				$nexY = $this->tab_top - 1;
 
@@ -728,8 +730,9 @@ class pdf_sponge extends ModelePDFFactures
 					}
 
 					$pdf->setTopMargin($this->tab_top_newpage);
-					$pdf->setPageOrientation('', 1, $this->heightforfooter + $this->heightforfreetext + $this->heightforinfotot); // The only function to edit the bottom margin of current page to set it.
 					$pageposbefore = $pdf->getPage();
+					$page_bottom_margin =  $this->heightforfooter + $this->heightforfreetext + $this->heightforinfotot + $this->getHeightForQRInvoice($pageposbefore, $object, $langs);
+					$pdf->setPageOrientation('', 1, $page_bottom_margin);
 
 					$showpricebeforepagebreak = 1;
 					$posYAfterImage = 0;
@@ -737,7 +740,7 @@ class pdf_sponge extends ModelePDFFactures
 
 					if ($this->getColumnStatus('photo')) {
 						// We start with Photo of product line
-						if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - ($this->heightforfooter + $this->heightforfreetext + $this->heightforinfotot))) {	// If photo too high, we moved completely on new page
+						if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - $page_bottom_margin)) {	// If photo too high, we moved completely on new page
 							$pdf->AddPage('', '', true);
 							if (!empty($tplidx)) {
 								$pdf->useTemplate($tplidx);
@@ -778,7 +781,7 @@ class pdf_sponge extends ModelePDFFactures
 							$pageposafter = $pdf->getPage();
 							$posyafter = $pdf->GetY();
 							//var_dump($posyafter); var_dump(($this->page_hauteur - ($this->heightforfooter+$this->heightforfreetext+$this->heightforinfotot))); exit;
-							if ($posyafter > ($this->page_hauteur - ($this->heightforfooter + $this->heightforfreetext + $this->heightforinfotot))) {	// There is no space left for total+free text
+							if ($posyafter > ($this->page_hauteur - $page_bottom_margin)) {	// There is no space left for total+free text
 								if ($i == ($nblines - 1)) {	// No more lines, and no space left to show total, so we create a new page
 									$pdf->AddPage('', '', true);
 									if (!empty($tplidx)) {
@@ -983,10 +986,11 @@ class pdf_sponge extends ModelePDFFactures
 					// Detect if some page were added automatically and output _tableau for past pages
 					while ($pagenb < $pageposafter) {
 						$pdf->setPage($pagenb);
+						$heightforqrinvoice = $this->getHeightForQRInvoice($pagenb, $object, $langs);
 						if ($pagenb == $pageposbeforeprintlines) {
-							$this->_tableau($pdf, $this->tab_top, $this->page_hauteur - $this->tab_top - $this->heightforfooter, 0, $outputlangs, $hidetop, 1, $object->multicurrency_code, $outputlangsbis);
+							$this->_tableau($pdf, $this->tab_top, $this->page_hauteur - $this->tab_top - $this->heightforfooter - $heightforqrinvoice, 0, $outputlangs, $hidetop, 1, $object->multicurrency_code, $outputlangsbis);
 						} else {
-							$this->_tableau($pdf, $this->tab_top_newpage, $this->page_hauteur - $this->tab_top_newpage - $this->heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code, $outputlangsbis);
+							$this->_tableau($pdf, $this->tab_top_newpage, $this->page_hauteur - $this->tab_top_newpage - $this->heightforfooter - $heightforqrinvoice, 0, $outputlangs, 1, 1, $object->multicurrency_code, $outputlangsbis);
 						}
 						$this->_pagefoot($pdf, $object, $outputlangs, 1);
 						$pagenb++;
@@ -1001,10 +1005,11 @@ class pdf_sponge extends ModelePDFFactures
 					}
 
 					if (isset($object->lines[$i + 1]->pagebreak) && $object->lines[$i + 1]->pagebreak) {
+						$heightforqrinvoice = $this->getHeightForQRInvoice($pagenb, $object, $langs);
 						if ($pagenb == $pageposafter) {
-							$this->_tableau($pdf, $this->tab_top, $this->page_hauteur - $this->tab_top - $this->heightforfooter, 0, $outputlangs, $hidetop, 1, $object->multicurrency_code, $outputlangsbis);
+							$this->_tableau($pdf, $this->tab_top, $this->page_hauteur - $this->tab_top - $this->heightforfooter - $heightforqrinvoice, 0, $outputlangs, $hidetop, 1, $object->multicurrency_code, $outputlangsbis);
 						} else {
-							$this->_tableau($pdf, $this->tab_top_newpage, $this->page_hauteur - $this->tab_top_newpage - $this->heightforfooter, 0, $outputlangs, 1, 1, $object->multicurrency_code, $outputlangsbis);
+							$this->_tableau($pdf, $this->tab_top_newpage, $this->page_hauteur - $this->tab_top_newpage - $this->heightforfooter - $heightforqrinvoice, 0, $outputlangs, 1, 1, $object->multicurrency_code, $outputlangsbis);
 						}
 						$this->_pagefoot($pdf, $object, $outputlangs, 1);
 						// New page
@@ -1020,12 +1025,13 @@ class pdf_sponge extends ModelePDFFactures
 				}
 
 				// Show square
+				$heightforqrinvoice = $this->getHeightForQRInvoice($pagenb, $object, $langs);
 				if ($pagenb == $pageposbeforeprintlines) {
-					$this->_tableau($pdf, $this->tab_top, $this->page_hauteur - $this->tab_top - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter, 0, $outputlangs, $hidetop, 0, $object->multicurrency_code, $outputlangsbis);
-					$bottomlasttab = $this->page_hauteur - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter + 1;
+					$this->_tableau($pdf, $this->tab_top, $this->page_hauteur - $this->tab_top - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter - $heightforqrinvoice, 0, $outputlangs, $hidetop, 0, $object->multicurrency_code, $outputlangsbis);
+					$bottomlasttab = $this->page_hauteur - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter - $heightforqrinvoice + 1;
 				} else {
-					$this->_tableau($pdf, $this->tab_top_newpage, $this->page_hauteur - $this->tab_top_newpage - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter, 0, $outputlangs, 1, 0, $object->multicurrency_code, $outputlangsbis);
-					$bottomlasttab = $this->page_hauteur - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter + 1;
+					$this->_tableau($pdf, $this->tab_top_newpage, $this->page_hauteur - $this->tab_top_newpage - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter - $heightforqrinvoice, 0, $outputlangs, 1, 0, $object->multicurrency_code, $outputlangsbis);
+					$bottomlasttab = $this->page_hauteur - $this->heightforinfotot - $this->heightforfreetext - $this->heightforfooter - $heightforqrinvoice + 1;
 				}
 
 				// Display infos area
@@ -1045,6 +1051,7 @@ class pdf_sponge extends ModelePDFFactures
 					$pdf->AliasNbPages();
 				}
 
+				$this->addSwissQRInvoice($pdf, $object, $outputlangs);
 				$pdf->Close();
 
 				$pdf->Output($file, 'F');
@@ -2447,7 +2454,7 @@ class pdf_sponge extends ModelePDFFactures
 	protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0)
 	{
 		$showdetails = getDolGlobalInt('MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS', 0);
-		return pdf_pagefoot($pdf, $outputlangs, 'INVOICE_FREE_TEXT', $this->emetteur, $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark);
+		return pdf_pagefoot($pdf, $outputlangs, 'INVOICE_FREE_TEXT', $this->emetteur, $this->getHeightForQRInvoice($pdf->getPage(), $object, $outputlangs) + $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext, $this->page_largeur, $this->watermark);
 	}
 
 	/**

+ 151 - 0
htdocs/core/modules/facture/modules_facture.php

@@ -31,6 +31,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/class/commondocgenerator.class.php';
 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
 require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; // Required because used in classes that inherit
 
+use \Sprain\SwissQrBill;
 
 /**
  *	Parent class of invoice document generators
@@ -78,6 +79,156 @@ abstract class ModelePDFFactures extends CommonDocGenerator
 
 		return $list;
 	}
+
+	/**
+	 * Get the SwissQR object, including validation
+	 *
+	 * @param Facture $object  Invoice object
+	 * @param Translate $langs Translation object
+	 * @return SwissQrBill|bool The valid SwissQR object, or false
+	 */
+	private function getSwissQrBill(\Facture $object, \Translate $langs) : SwissQrBill\QrBill|bool
+	{
+		global $conf;
+
+		if ($object->mode_reglement_code != 'VIR') {
+			$this->error = $langs->transnoentities("SwissQrOnlyVIR");
+			return false;
+		}
+
+		if (empty($conf->global->INVOICE_ADD_SWISS_QR_CODE)) {
+			return false;
+		}
+
+		require_once DOL_DOCUMENT_ROOT.'/includes/sprain/swiss-qr-bill/autoload.php';
+
+		// Create a new instance of SwissQrBill, containing default headers with fixed values
+		$qrBill = SwissQrBill\QrBill::create();
+
+		// First, check creditor address
+		$address = SwissQrBill\DataGroup\Element\CombinedAddress::create(
+			$this->emetteur->name,
+			$this->emetteur->address,
+			$this->emetteur->zip . " " . $this->emetteur->town,
+			$this->emetteur->country_code
+		);
+		if (!$address->isValid()) {
+			$this->error = $langs->transnoentities("SwissQrCreditorAddressInvalid", (string) $address->getViolations());
+			return false;
+		}
+		$qrBill->setCreditor($address);
+
+		// Get IBAN from account.
+		$account = new Account($this->db);
+		$account->fetch($object->fk_account);
+
+		$creditorInformation = SwissQrBill\DataGroup\Element\CreditorInformation::create($account->iban);
+		if (!$creditorInformation->isValid()) {
+			$this->error = $langs->transnoentities("SwissQrCreditorInformationInvalid", $account->iban, (string) $creditorInformation->getViolations());
+			return false;
+		}
+		$qrBill->setCreditorInformation($creditorInformation);
+
+		if ($creditorInformation->containsQrIban()) {
+			$this->error = $langs->transnoentities("SwissQrIbanNotImplementedYet", $account->iban);
+			return false;
+		}
+
+		// Add payment reference CLASSIC-IBAN
+		// This is what you will need to identify incoming payments.
+		$qrBill->setPaymentReference(
+			SwissQrBill\DataGroup\Element\PaymentReference::create(
+				SwissQrBill\DataGroup\Element\PaymentReference::TYPE_NON,
+			)
+		);
+
+		// Add payment amount, with currency
+		$pai = SwissQrBill\DataGroup\Element\PaymentAmountInformation::create($object->multicurrency_code, $object->total_ttc);
+		if (!$pai->isValid()) {
+			$this->error = $langs->transnoentities("SwissQrPaymentInformationInvalid", $object->total_ttc, (string) $pai->getViolations());
+			return false;
+		}
+		$qrBill->setPaymentAmountInformation($pai);
+
+		// Add some human-readable information about what the bill is for.
+		$qrBill->setAdditionalInformation(
+			SwissQrBill\DataGroup\Element\AdditionalInformation::create(
+				$object->ref
+			)
+		);
+
+		// Check debtor address; We _know_ zip&town have to be filled, so skip that if unfilled.
+		if (!empty($object->thirdparty->zip) && !empty($object->thirdparty->town)) {
+			$address = SwissQrBill\DataGroup\Element\CombinedAddress::create(
+				$object->thirdparty->name,
+				$object->thirdparty->address,
+				$object->thirdparty->zip . " " . $object->thirdparty->town,
+				$object->thirdparty->country_code
+			);
+			if (!$address->isValid()) {
+				$this->error = $langs->transnoentities("SwissQrDebitorAddressInvalid", (string) $address->getViolations());
+				return false;
+			}
+			$qrBill->setUltimateDebtor($address);
+		}
+
+		return $qrBill;
+	}
+
+	/**
+	 * Get the height for bottom-page QR invoice in mm, depending on the page number.
+	 *
+	 * @param int       $pagenbr Page number
+	 * @param Facture   $object  Invoice object
+	 * @param Translate $langs   Translation object
+	 * @return int      Height in mm of the bottom-page QR invoice. Can be zero if not on right page; not enabled
+	 */
+	protected function getHeightForQRInvoice(int $pagenbr, \Facture $object, \Translate $langs) : int
+	{
+		global $conf;
+
+		// Keep it, reset it after QRinvoice getter
+		$error = $this->error;
+
+		if (!empty($conf->global->INVOICE_ADD_SWISS_QR_CODE)) {
+			if (!$this->getSwissQrBill($object, $langs)) {
+				// Reset error to previous one if exists
+				$this->error = $error;
+				return 0;
+			}
+			// SWIFT's requirementis 105, but we get more room with 100 and the page nuber is in a nice place.
+			return $pagenbr == 1 ? 100 : 0;
+		}
+		return 0;
+	}
+
+	/**
+	 * Add SwissQR invoice at bottom of page 1
+	 *
+	 * @param TCPDF     $pdf     TCPDF object
+	 * @param Facture   $object  Invoice object
+	 * @param Translate $langs   Translation object
+	 * @return bool for success
+	 */
+	public function addSwissQRInvoice(\TCPDF $pdf, \Facture $object, \Translate $langs) : bool
+	{
+		if (!($qrBill = $this->getSwissQrBill($object, $langs))) {
+			return false;
+		}
+
+		try {
+			$pdf->startTransaction();
+
+			$pdf->setPage(1);
+			$pdf->SetTextColor(0, 0, 0);
+			$output = new SwissQrBill\PaymentPart\Output\TcPdfOutput\TcPdfOutput($qrBill, in_array($langs->shortlang, ['de', 'fr', 'it']) ? $langs->shortlang : 'en', $pdf);
+			$output->setPrintable(false)->getPaymentPart();
+		} catch (Exception $e) {
+			$pdf->rollbackTransaction(true);
+			return false;
+		}
+		return true;
+	}
 }
 
 /**

+ 22 - 0
htdocs/includes/bacon/bacon-qr-code/LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2017, Ben Scholzen 'DASPRiD'
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 39 - 0
htdocs/includes/bacon/bacon-qr-code/README.md

@@ -0,0 +1,39 @@
+# QR Code generator
+
+[![PHP CI](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml/badge.svg)](https://github.com/Bacon/BaconQrCode/actions/workflows/ci.yml)
+[![codecov](https://codecov.io/gh/Bacon/BaconQrCode/branch/master/graph/badge.svg?token=rD0HcAiEEx)](https://codecov.io/gh/Bacon/BaconQrCode)
+[![Latest Stable Version](https://poser.pugx.org/bacon/bacon-qr-code/v/stable)](https://packagist.org/packages/bacon/bacon-qr-code)
+[![Total Downloads](https://poser.pugx.org/bacon/bacon-qr-code/downloads)](https://packagist.org/packages/bacon/bacon-qr-code)
+[![License](https://poser.pugx.org/bacon/bacon-qr-code/license)](https://packagist.org/packages/bacon/bacon-qr-code)
+
+
+## Introduction
+BaconQrCode is a port of QR code portion of the ZXing library. It currently
+only features the encoder part, but could later receive the decoder part as
+well.
+
+As the Reed Solomon codec implementation of the ZXing library performs quite
+slow in PHP, it was exchanged with the implementation by Phil Karn.
+
+
+## Example usage
+```php
+use BaconQrCode\Renderer\ImageRenderer;
+use BaconQrCode\Renderer\Image\ImagickImageBackEnd;
+use BaconQrCode\Renderer\RendererStyle\RendererStyle;
+use BaconQrCode\Writer;
+
+$renderer = new ImageRenderer(
+    new RendererStyle(400),
+    new ImagickImageBackEnd()
+);
+$writer = new Writer($renderer);
+$writer->writeFile('Hello World!', 'qrcode.png');
+```
+
+## Available image renderer back ends
+BaconQrCode comes with multiple back ends for rendering images. Currently included are the following:
+
+- `ImagickImageBackEnd`: renders raster images using the Imagick library
+- `SvgImageBackEnd`: renders SVG files using XMLWriter
+- `EpsImageBackEnd`: renders EPS files

+ 44 - 0
htdocs/includes/bacon/bacon-qr-code/composer.json.disabled

@@ -0,0 +1,44 @@
+{
+    "name": "bacon/bacon-qr-code",
+    "description": "BaconQrCode is a QR code generator for PHP.",
+    "license" : "BSD-2-Clause",
+    "homepage": "https://github.com/Bacon/BaconQrCode",
+    "require": {
+        "php": "^7.1 || ^8.0",
+        "ext-iconv": "*",
+        "dasprid/enum": "^1.0.3"
+    },
+    "suggest": {
+        "ext-imagick": "to generate QR code images"
+    },
+    "authors": [
+        {
+            "name": "Ben Scholzen 'DASPRiD'",
+            "email": "mail@dasprids.de",
+            "homepage": "https://dasprids.de/",
+            "role": "Developer"
+        }
+    ],
+    "autoload": {
+        "psr-4": {
+            "BaconQrCode\\": "src/"
+        }
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^7 | ^8 | ^9",
+        "spatie/phpunit-snapshot-assertions": "^4.2.9",
+        "squizlabs/php_codesniffer": "^3.4",
+        "phly/keep-a-changelog": "^2.1"
+    },
+    "config": {
+        "allow-plugins": {
+            "ocramius/package-versions": true
+        }
+    },
+    "archive": {
+        "exclude": [
+            "/test",
+            "/phpunit.xml.dist"
+        ]
+    }
+}

+ 372 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/BitArray.php

@@ -0,0 +1,372 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+use BaconQrCode\Exception\InvalidArgumentException;
+use SplFixedArray;
+
+/**
+ * A simple, fast array of bits.
+ */
+final class BitArray
+{
+    /**
+     * Bits represented as an array of integers.
+     *
+     * @var SplFixedArray<int>
+     */
+    private $bits;
+
+    /**
+     * Size of the bit array in bits.
+     *
+     * @var int
+     */
+    private $size;
+
+    /**
+     * Creates a new bit array with a given size.
+     */
+    public function __construct(int $size = 0)
+    {
+        $this->size = $size;
+        $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0));
+    }
+
+    /**
+     * Gets the size in bits.
+     */
+    public function getSize() : int
+    {
+        return $this->size;
+    }
+
+    /**
+     * Gets the size in bytes.
+     */
+    public function getSizeInBytes() : int
+    {
+        return ($this->size + 7) >> 3;
+    }
+
+    /**
+     * Ensures that the array has a minimum capacity.
+     */
+    public function ensureCapacity(int $size) : void
+    {
+        if ($size > count($this->bits) << 5) {
+            $this->bits->setSize(($size + 31) >> 5);
+        }
+    }
+
+    /**
+     * Gets a specific bit.
+     */
+    public function get(int $i) : bool
+    {
+        return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f)));
+    }
+
+    /**
+     * Sets a specific bit.
+     */
+    public function set(int $i) : void
+    {
+        $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
+    }
+
+    /**
+     * Flips a specific bit.
+     */
+    public function flip(int $i) : void
+    {
+        $this->bits[$i >> 5] ^= 1 << ($i & 0x1f);
+    }
+
+    /**
+     * Gets the next set bit position from a given position.
+     */
+    public function getNextSet(int $from) : int
+    {
+        if ($from >= $this->size) {
+            return $this->size;
+        }
+
+        $bitsOffset = $from >> 5;
+        $currentBits = $this->bits[$bitsOffset];
+        $bitsLength = count($this->bits);
+        $currentBits &= ~((1 << ($from & 0x1f)) - 1);
+
+        while (0 === $currentBits) {
+            if (++$bitsOffset === $bitsLength) {
+                return $this->size;
+            }
+
+            $currentBits = $this->bits[$bitsOffset];
+        }
+
+        $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
+        return $result > $this->size ? $this->size : $result;
+    }
+
+    /**
+     * Gets the next unset bit position from a given position.
+     */
+    public function getNextUnset(int $from) : int
+    {
+        if ($from >= $this->size) {
+            return $this->size;
+        }
+
+        $bitsOffset = $from >> 5;
+        $currentBits = ~$this->bits[$bitsOffset];
+        $bitsLength = count($this->bits);
+        $currentBits &= ~((1 << ($from & 0x1f)) - 1);
+
+        while (0 === $currentBits) {
+            if (++$bitsOffset === $bitsLength) {
+                return $this->size;
+            }
+
+            $currentBits = ~$this->bits[$bitsOffset];
+        }
+
+        $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
+        return $result > $this->size ? $this->size : $result;
+    }
+
+    /**
+     * Sets a bulk of bits.
+     */
+    public function setBulk(int $i, int $newBits) : void
+    {
+        $this->bits[$i >> 5] = $newBits;
+    }
+
+    /**
+     * Sets a range of bits.
+     *
+     * @throws InvalidArgumentException if end is smaller than start
+     */
+    public function setRange(int $start, int $end) : void
+    {
+        if ($end < $start) {
+            throw new InvalidArgumentException('End must be greater or equal to start');
+        }
+
+        if ($end === $start) {
+            return;
+        }
+
+        --$end;
+
+        $firstInt = $start >> 5;
+        $lastInt = $end >> 5;
+
+        for ($i = $firstInt; $i <= $lastInt; ++$i) {
+            $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
+            $lastBit = $i < $lastInt ? 31 : $end & 0x1f;
+
+            if (0 === $firstBit && 31 === $lastBit) {
+                $mask = 0x7fffffff;
+            } else {
+                $mask = 0;
+
+                for ($j = $firstBit; $j < $lastBit; ++$j) {
+                    $mask |= 1 << $j;
+                }
+            }
+
+            $this->bits[$i] = $this->bits[$i] | $mask;
+        }
+    }
+
+    /**
+     * Clears the bit array, unsetting every bit.
+     */
+    public function clear() : void
+    {
+        $bitsLength = count($this->bits);
+
+        for ($i = 0; $i < $bitsLength; ++$i) {
+            $this->bits[$i] = 0;
+        }
+    }
+
+    /**
+     * Checks if a range of bits is set or not set.
+
+     * @throws InvalidArgumentException if end is smaller than start
+     */
+    public function isRange(int $start, int $end, bool $value) : bool
+    {
+        if ($end < $start) {
+            throw new InvalidArgumentException('End must be greater or equal to start');
+        }
+
+        if ($end === $start) {
+            return true;
+        }
+
+        --$end;
+
+        $firstInt = $start >> 5;
+        $lastInt = $end >> 5;
+
+        for ($i = $firstInt; $i <= $lastInt; ++$i) {
+            $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
+            $lastBit = $i < $lastInt ? 31 : $end & 0x1f;
+
+            if (0 === $firstBit && 31 === $lastBit) {
+                $mask = 0x7fffffff;
+            } else {
+                $mask = 0;
+
+                for ($j = $firstBit; $j <= $lastBit; ++$j) {
+                    $mask |= 1 << $j;
+                }
+            }
+
+            if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Appends a bit to the array.
+     */
+    public function appendBit(bool $bit) : void
+    {
+        $this->ensureCapacity($this->size + 1);
+
+        if ($bit) {
+            $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
+        }
+
+        ++$this->size;
+    }
+
+    /**
+     * Appends a number of bits (up to 32) to the array.
+
+     * @throws InvalidArgumentException if num bits is not between 0 and 32
+     */
+    public function appendBits(int $value, int $numBits) : void
+    {
+        if ($numBits < 0 || $numBits > 32) {
+            throw new InvalidArgumentException('Num bits must be between 0 and 32');
+        }
+
+        $this->ensureCapacity($this->size + $numBits);
+
+        for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
+            $this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
+        }
+    }
+
+    /**
+     * Appends another bit array to this array.
+     */
+    public function appendBitArray(self $other) : void
+    {
+        $otherSize = $other->getSize();
+        $this->ensureCapacity($this->size + $other->getSize());
+
+        for ($i = 0; $i < $otherSize; ++$i) {
+            $this->appendBit($other->get($i));
+        }
+    }
+
+    /**
+     * Makes an exclusive-or comparision on the current bit array.
+     *
+     * @throws InvalidArgumentException if sizes don't match
+     */
+    public function xorBits(self $other) : void
+    {
+        $bitsLength = count($this->bits);
+        $otherBits  = $other->getBitArray();
+
+        if ($bitsLength !== count($otherBits)) {
+            throw new InvalidArgumentException('Sizes don\'t match');
+        }
+
+        for ($i = 0; $i < $bitsLength; ++$i) {
+            $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
+        }
+    }
+
+    /**
+     * Converts the bit array to a byte array.
+     *
+     * @return SplFixedArray<int>
+     */
+    public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray
+    {
+        $bytes = new SplFixedArray($numBytes);
+
+        for ($i = 0; $i < $numBytes; ++$i) {
+            $byte = 0;
+
+            for ($j = 0; $j < 8; ++$j) {
+                if ($this->get($bitOffset)) {
+                    $byte |= 1 << (7 - $j);
+                }
+
+                ++$bitOffset;
+            }
+
+            $bytes[$i] = $byte;
+        }
+
+        return $bytes;
+    }
+
+    /**
+     * Gets the internal bit array.
+     *
+     * @return SplFixedArray<int>
+     */
+    public function getBitArray() : SplFixedArray
+    {
+        return $this->bits;
+    }
+
+    /**
+     * Reverses the array.
+     */
+    public function reverse() : void
+    {
+        $newBits = new SplFixedArray(count($this->bits));
+
+        for ($i = 0; $i < $this->size; ++$i) {
+            if ($this->get($this->size - $i - 1)) {
+                $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f));
+            }
+        }
+
+        $this->bits = $newBits;
+    }
+
+    /**
+     * Returns a string representation of the bit array.
+     */
+    public function __toString() : string
+    {
+        $result = '';
+
+        for ($i = 0; $i < $this->size; ++$i) {
+            if (0 === ($i & 0x07)) {
+                $result .= ' ';
+            }
+
+            $result .= $this->get($i) ? 'X' : '.';
+        }
+
+        return $result;
+    }
+}

+ 313 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/BitMatrix.php

@@ -0,0 +1,313 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+use BaconQrCode\Exception\InvalidArgumentException;
+use SplFixedArray;
+
+/**
+ * Bit matrix.
+ *
+ * Represents a 2D matrix of bits. In function arguments below, and throughout
+ * the common module, x is the column position, and y is the row position. The
+ * ordering is always x, y. The origin is at the top-left.
+ */
+class BitMatrix
+{
+    /**
+     * Width of the bit matrix.
+     *
+     * @var int
+     */
+    private $width;
+
+    /**
+     * Height of the bit matrix.
+     *
+     * @var int
+     */
+    private $height;
+
+    /**
+     * Size in bits of each individual row.
+     *
+     * @var int
+     */
+    private $rowSize;
+
+    /**
+     * Bits representation.
+     *
+     * @var SplFixedArray<int>
+     */
+    private $bits;
+
+    /**
+     * @throws InvalidArgumentException if a dimension is smaller than zero
+     */
+    public function __construct(int $width, int $height = null)
+    {
+        if (null === $height) {
+            $height = $width;
+        }
+
+        if ($width < 1 || $height < 1) {
+            throw new InvalidArgumentException('Both dimensions must be greater than zero');
+        }
+
+        $this->width = $width;
+        $this->height = $height;
+        $this->rowSize = ($width + 31) >> 5;
+        $this->bits = SplFixedArray::fromArray(array_fill(0, $this->rowSize * $height, 0));
+    }
+
+    /**
+     * Gets the requested bit, where true means black.
+     */
+    public function get(int $x, int $y) : bool
+    {
+        $offset = $y * $this->rowSize + ($x >> 5);
+        return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], ($x & 0x1f)) & 1);
+    }
+
+    /**
+     * Sets the given bit to true.
+     */
+    public function set(int $x, int $y) : void
+    {
+        $offset = $y * $this->rowSize + ($x >> 5);
+        $this->bits[$offset] = $this->bits[$offset] | (1 << ($x & 0x1f));
+    }
+
+    /**
+     * Flips the given bit.
+     */
+    public function flip(int $x, int $y) : void
+    {
+        $offset = $y * $this->rowSize + ($x >> 5);
+        $this->bits[$offset] = $this->bits[$offset] ^ (1 << ($x & 0x1f));
+    }
+
+    /**
+     * Clears all bits (set to false).
+     */
+    public function clear() : void
+    {
+        $max = count($this->bits);
+
+        for ($i = 0; $i < $max; ++$i) {
+            $this->bits[$i] = 0;
+        }
+    }
+
+    /**
+     * Sets a square region of the bit matrix to true.
+     *
+     * @throws InvalidArgumentException if left or top are negative
+     * @throws InvalidArgumentException if width or height are smaller than 1
+     * @throws InvalidArgumentException if region does not fit into the matix
+     */
+    public function setRegion(int $left, int $top, int $width, int $height) : void
+    {
+        if ($top < 0 || $left < 0) {
+            throw new InvalidArgumentException('Left and top must be non-negative');
+        }
+
+        if ($height < 1 || $width < 1) {
+            throw new InvalidArgumentException('Width and height must be at least 1');
+        }
+
+        $right = $left + $width;
+        $bottom = $top + $height;
+
+        if ($bottom > $this->height || $right > $this->width) {
+            throw new InvalidArgumentException('The region must fit inside the matrix');
+        }
+
+        for ($y = $top; $y < $bottom; ++$y) {
+            $offset = $y * $this->rowSize;
+
+            for ($x = $left; $x < $right; ++$x) {
+                $index = $offset + ($x >> 5);
+                $this->bits[$index] = $this->bits[$index] | (1 << ($x & 0x1f));
+            }
+        }
+    }
+
+    /**
+     * A fast method to retrieve one row of data from the matrix as a BitArray.
+     */
+    public function getRow(int $y, BitArray $row = null) : BitArray
+    {
+        if (null === $row || $row->getSize() < $this->width) {
+            $row = new BitArray($this->width);
+        }
+
+        $offset = $y * $this->rowSize;
+
+        for ($x = 0; $x < $this->rowSize; ++$x) {
+            $row->setBulk($x << 5, $this->bits[$offset + $x]);
+        }
+
+        return $row;
+    }
+
+    /**
+     * Sets a row of data from a BitArray.
+     */
+    public function setRow(int $y, BitArray $row) : void
+    {
+        $bits = $row->getBitArray();
+
+        for ($i = 0; $i < $this->rowSize; ++$i) {
+            $this->bits[$y * $this->rowSize + $i] = $bits[$i];
+        }
+    }
+
+    /**
+     * This is useful in detecting the enclosing rectangle of a 'pure' barcode.
+     *
+     * @return int[]|null
+     */
+    public function getEnclosingRectangle() : ?array
+    {
+        $left = $this->width;
+        $top = $this->height;
+        $right = -1;
+        $bottom = -1;
+
+        for ($y = 0; $y < $this->height; ++$y) {
+            for ($x32 = 0; $x32 < $this->rowSize; ++$x32) {
+                $bits = $this->bits[$y * $this->rowSize + $x32];
+
+                if (0 !== $bits) {
+                    if ($y < $top) {
+                        $top = $y;
+                    }
+
+                    if ($y > $bottom) {
+                        $bottom = $y;
+                    }
+
+                    if ($x32 * 32 < $left) {
+                        $bit = 0;
+
+                        while (($bits << (31 - $bit)) === 0) {
+                            $bit++;
+                        }
+
+                        if (($x32 * 32 + $bit) < $left) {
+                            $left = $x32 * 32 + $bit;
+                        }
+                    }
+                }
+
+                if ($x32 * 32 + 31 > $right) {
+                    $bit = 31;
+
+                    while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
+                        --$bit;
+                    }
+
+                    if (($x32 * 32 + $bit) > $right) {
+                        $right = $x32 * 32 + $bit;
+                    }
+                }
+            }
+        }
+
+        $width = $right - $left;
+        $height = $bottom - $top;
+
+        if ($width < 0 || $height < 0) {
+            return null;
+        }
+
+        return [$left, $top, $width, $height];
+    }
+
+    /**
+     * Gets the most top left set bit.
+     *
+     * This is useful in detecting a corner of a 'pure' barcode.
+     *
+     * @return int[]|null
+     */
+    public function getTopLeftOnBit() : ?array
+    {
+        $bitsOffset = 0;
+
+        while ($bitsOffset < count($this->bits) && 0 === $this->bits[$bitsOffset]) {
+            ++$bitsOffset;
+        }
+
+        if (count($this->bits) === $bitsOffset) {
+            return null;
+        }
+
+        $x = intdiv($bitsOffset, $this->rowSize);
+        $y = ($bitsOffset % $this->rowSize) << 5;
+
+        $bits = $this->bits[$bitsOffset];
+        $bit = 0;
+
+        while (0 === ($bits << (31 - $bit))) {
+            ++$bit;
+        }
+
+        $x += $bit;
+
+        return [$x, $y];
+    }
+
+    /**
+     * Gets the most bottom right set bit.
+     *
+     * This is useful in detecting a corner of a 'pure' barcode.
+     *
+     * @return int[]|null
+     */
+    public function getBottomRightOnBit() : ?array
+    {
+        $bitsOffset = count($this->bits) - 1;
+
+        while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) {
+            --$bitsOffset;
+        }
+
+        if ($bitsOffset < 0) {
+            return null;
+        }
+
+        $x = intdiv($bitsOffset, $this->rowSize);
+        $y = ($bitsOffset % $this->rowSize) << 5;
+
+        $bits = $this->bits[$bitsOffset];
+        $bit  = 0;
+
+        while (0 === BitUtils::unsignedRightShift($bits, $bit)) {
+            --$bit;
+        }
+
+        $x += $bit;
+
+        return [$x, $y];
+    }
+
+    /**
+     * Gets the width of the matrix,
+     */
+    public function getWidth() : int
+    {
+        return $this->width;
+    }
+
+    /**
+     * Gets the height of the matrix.
+     */
+    public function getHeight() : int
+    {
+        return $this->height;
+    }
+}

+ 41 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/BitUtils.php

@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+/**
+ * General bit utilities.
+ *
+ * All utility methods are based on 32-bit integers and also work on 64-bit
+ * systems.
+ */
+final class BitUtils
+{
+    private function __construct()
+    {
+    }
+
+    /**
+     * Performs an unsigned right shift.
+     *
+     * This is the same as the unsigned right shift operator ">>>" in other
+     * languages.
+     */
+    public static function unsignedRightShift(int $a, int $b) : int
+    {
+        return (
+            $a >= 0
+            ? $a >> $b
+            : (($a & 0x7fffffff) >> $b) | (0x40000000 >> ($b - 1))
+        );
+    }
+
+    /**
+     * Gets the number of trailing zeros.
+     */
+    public static function numberOfTrailingZeros(int $i) : int
+    {
+        $lastPos = strrpos(str_pad(decbin($i), 32, '0', STR_PAD_LEFT), '1');
+        return $lastPos === false ? 32 : 31 - $lastPos;
+    }
+}

+ 183 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/CharacterSetEci.php

@@ -0,0 +1,183 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+use BaconQrCode\Exception\InvalidArgumentException;
+use DASPRiD\Enum\AbstractEnum;
+
+/**
+ * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1 of ISO 18004.
+ *
+ * @method static self CP437()
+ * @method static self ISO8859_1()
+ * @method static self ISO8859_2()
+ * @method static self ISO8859_3()
+ * @method static self ISO8859_4()
+ * @method static self ISO8859_5()
+ * @method static self ISO8859_6()
+ * @method static self ISO8859_7()
+ * @method static self ISO8859_8()
+ * @method static self ISO8859_9()
+ * @method static self ISO8859_10()
+ * @method static self ISO8859_11()
+ * @method static self ISO8859_12()
+ * @method static self ISO8859_13()
+ * @method static self ISO8859_14()
+ * @method static self ISO8859_15()
+ * @method static self ISO8859_16()
+ * @method static self SJIS()
+ * @method static self CP1250()
+ * @method static self CP1251()
+ * @method static self CP1252()
+ * @method static self CP1256()
+ * @method static self UNICODE_BIG_UNMARKED()
+ * @method static self UTF8()
+ * @method static self ASCII()
+ * @method static self BIG5()
+ * @method static self GB18030()
+ * @method static self EUC_KR()
+ */
+final class CharacterSetEci extends AbstractEnum
+{
+    protected const CP437 = [[0, 2]];
+    protected const ISO8859_1 = [[1, 3], 'ISO-8859-1'];
+    protected const ISO8859_2 = [[4], 'ISO-8859-2'];
+    protected const ISO8859_3 = [[5], 'ISO-8859-3'];
+    protected const ISO8859_4 = [[6], 'ISO-8859-4'];
+    protected const ISO8859_5 = [[7], 'ISO-8859-5'];
+    protected const ISO8859_6 = [[8], 'ISO-8859-6'];
+    protected const ISO8859_7 = [[9], 'ISO-8859-7'];
+    protected const ISO8859_8 = [[10], 'ISO-8859-8'];
+    protected const ISO8859_9 = [[11], 'ISO-8859-9'];
+    protected const ISO8859_10 = [[12], 'ISO-8859-10'];
+    protected const ISO8859_11 = [[13], 'ISO-8859-11'];
+    protected const ISO8859_12 = [[14], 'ISO-8859-12'];
+    protected const ISO8859_13 = [[15], 'ISO-8859-13'];
+    protected const ISO8859_14 = [[16], 'ISO-8859-14'];
+    protected const ISO8859_15 = [[17], 'ISO-8859-15'];
+    protected const ISO8859_16 = [[18], 'ISO-8859-16'];
+    protected const SJIS = [[20], 'Shift_JIS'];
+    protected const CP1250 = [[21], 'windows-1250'];
+    protected const CP1251 = [[22], 'windows-1251'];
+    protected const CP1252 = [[23], 'windows-1252'];
+    protected const CP1256 = [[24], 'windows-1256'];
+    protected const UNICODE_BIG_UNMARKED = [[25], 'UTF-16BE', 'UnicodeBig'];
+    protected const UTF8 = [[26], 'UTF-8'];
+    protected const ASCII = [[27, 170], 'US-ASCII'];
+    protected const BIG5 = [[28]];
+    protected const GB18030 = [[29], 'GB2312', 'EUC_CN', 'GBK'];
+    protected const EUC_KR = [[30], 'EUC-KR'];
+
+    /**
+     * @var int[]
+     */
+    private $values;
+
+    /**
+     * @var string[]
+     */
+    private $otherEncodingNames;
+
+    /**
+     * @var array<int, self>|null
+     */
+    private static $valueToEci;
+
+    /**
+     * @var array<string, self>|null
+     */
+    private static $nameToEci;
+
+    /**
+     * @param int[] $values
+     */
+    public function __construct(array $values, string ...$otherEncodingNames)
+    {
+        $this->values = $values;
+        $this->otherEncodingNames = $otherEncodingNames;
+    }
+
+    /**
+     * Returns the primary value.
+     */
+    public function getValue() : int
+    {
+        return $this->values[0];
+    }
+
+    /**
+     * Gets character set ECI by value.
+     *
+     * Returns the representing ECI of a given value, or null if it is legal but unsupported.
+     *
+     * @throws InvalidArgumentException if value is not between 0 and 900
+     */
+    public static function getCharacterSetEciByValue(int $value) : ?self
+    {
+        if ($value < 0 || $value >= 900) {
+            throw new InvalidArgumentException('Value must be between 0 and 900');
+        }
+
+        $valueToEci = self::valueToEci();
+
+        if (! array_key_exists($value, $valueToEci)) {
+            return null;
+        }
+
+        return $valueToEci[$value];
+    }
+
+    /**
+     * Returns character set ECI by name.
+     *
+     * Returns the representing ECI of a given name, or null if it is legal but unsupported
+     */
+    public static function getCharacterSetEciByName(string $name) : ?self
+    {
+        $nameToEci = self::nameToEci();
+        $name = strtolower($name);
+
+        if (! array_key_exists($name, $nameToEci)) {
+            return null;
+        }
+
+        return $nameToEci[$name];
+    }
+
+    private static function valueToEci() : array
+    {
+        if (null !== self::$valueToEci) {
+            return self::$valueToEci;
+        }
+
+        self::$valueToEci = [];
+
+        foreach (self::values() as $eci) {
+            foreach ($eci->values as $value) {
+                self::$valueToEci[$value] = $eci;
+            }
+        }
+
+        return self::$valueToEci;
+    }
+
+    private static function nameToEci() : array
+    {
+        if (null !== self::$nameToEci) {
+            return self::$nameToEci;
+        }
+
+        self::$nameToEci = [];
+
+        foreach (self::values() as $eci) {
+            self::$nameToEci[strtolower($eci->name())] = $eci;
+
+            foreach ($eci->otherEncodingNames as $name) {
+                self::$nameToEci[strtolower($name)] = $eci;
+            }
+        }
+
+        return self::$nameToEci;
+    }
+}

+ 49 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/EcBlock.php

@@ -0,0 +1,49 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+/**
+ * Encapsulates the parameters for one error-correction block in one symbol version.
+ *
+ * This includes the number of data codewords, and the number of times a block with these parameters is used
+ * consecutively in the QR code version's format.
+ */
+final class EcBlock
+{
+    /**
+     * How many times the block is used.
+     *
+     * @var int
+     */
+    private $count;
+
+    /**
+     * Number of data codewords.
+     *
+     * @var int
+     */
+    private $dataCodewords;
+
+    public function __construct(int $count, int $dataCodewords)
+    {
+        $this->count = $count;
+        $this->dataCodewords = $dataCodewords;
+    }
+
+    /**
+     * Returns how many times the block is used.
+     */
+    public function getCount() : int
+    {
+        return $this->count;
+    }
+
+    /**
+     * Returns the number of data codewords.
+     */
+    public function getDataCodewords() : int
+    {
+        return $this->dataCodewords;
+    }
+}

+ 74 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/EcBlocks.php

@@ -0,0 +1,74 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+/**
+ * Encapsulates a set of error-correction blocks in one symbol version.
+ *
+ * Most versions will use blocks of differing sizes within one version, so, this encapsulates the parameters for each
+ * set of blocks. It also holds the number of error-correction codewords per block since it will be the same across all
+ * blocks within one version.
+ */
+final class EcBlocks
+{
+    /**
+     * Number of EC codewords per block.
+     *
+     * @var int
+     */
+    private $ecCodewordsPerBlock;
+
+    /**
+     * List of EC blocks.
+     *
+     * @var EcBlock[]
+     */
+    private $ecBlocks;
+
+    public function __construct(int $ecCodewordsPerBlock, EcBlock ...$ecBlocks)
+    {
+        $this->ecCodewordsPerBlock = $ecCodewordsPerBlock;
+        $this->ecBlocks = $ecBlocks;
+    }
+
+    /**
+     * Returns the number of EC codewords per block.
+     */
+    public function getEcCodewordsPerBlock() : int
+    {
+        return $this->ecCodewordsPerBlock;
+    }
+
+    /**
+     * Returns the total number of EC block appearances.
+     */
+    public function getNumBlocks() : int
+    {
+        $total = 0;
+
+        foreach ($this->ecBlocks as $ecBlock) {
+            $total += $ecBlock->getCount();
+        }
+
+        return $total;
+    }
+
+    /**
+     * Returns the total count of EC codewords.
+     */
+    public function getTotalEcCodewords() : int
+    {
+        return $this->ecCodewordsPerBlock * $this->getNumBlocks();
+    }
+
+    /**
+     * Returns the EC blocks included in this collection.
+     *
+     * @return EcBlock[]
+     */
+    public function getEcBlocks() : array
+    {
+        return $this->ecBlocks;
+    }
+}

+ 63 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/ErrorCorrectionLevel.php

@@ -0,0 +1,63 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+use BaconQrCode\Exception\OutOfBoundsException;
+use DASPRiD\Enum\AbstractEnum;
+
+/**
+ * Enum representing the four error correction levels.
+ *
+ * @method static self L() ~7% correction
+ * @method static self M() ~15% correction
+ * @method static self Q() ~25% correction
+ * @method static self H() ~30% correction
+ */
+final class ErrorCorrectionLevel extends AbstractEnum
+{
+    protected const L = [0x01];
+    protected const M = [0x00];
+    protected const Q = [0x03];
+    protected const H = [0x02];
+
+    /**
+     * @var int
+     */
+    private $bits;
+
+    protected function __construct(int $bits)
+    {
+        $this->bits = $bits;
+    }
+
+    /**
+     * @throws OutOfBoundsException if number of bits is invalid
+     */
+    public static function forBits(int $bits) : self
+    {
+        switch ($bits) {
+            case 0:
+                return self::M();
+
+            case 1:
+                return self::L();
+
+            case 2:
+                return self::H();
+
+            case 3:
+                return self::Q();
+        }
+
+        throw new OutOfBoundsException('Invalid number of bits');
+    }
+
+    /**
+     * Returns the two bits used to encode this error correction level.
+     */
+    public function getBits() : int
+    {
+        return $this->bits;
+    }
+}

+ 203 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/FormatInformation.php

@@ -0,0 +1,203 @@
+<?php
+/**
+ * BaconQrCode
+ *
+ * @link      http://github.com/Bacon/BaconQrCode For the canonical source repository
+ * @copyright 2013 Ben 'DASPRiD' Scholzen
+ * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
+ */
+
+namespace BaconQrCode\Common;
+
+/**
+ * Encapsulates a QR Code's format information, including the data mask used and error correction level.
+ */
+class FormatInformation
+{
+    /**
+     * Mask for format information.
+     */
+    private const FORMAT_INFO_MASK_QR = 0x5412;
+
+    /**
+     * Lookup table for decoding format information.
+     *
+     * See ISO 18004:2006, Annex C, Table C.1
+     */
+    private const FORMAT_INFO_DECODE_LOOKUP = [
+        [0x5412, 0x00],
+        [0x5125, 0x01],
+        [0x5e7c, 0x02],
+        [0x5b4b, 0x03],
+        [0x45f9, 0x04],
+        [0x40ce, 0x05],
+        [0x4f97, 0x06],
+        [0x4aa0, 0x07],
+        [0x77c4, 0x08],
+        [0x72f3, 0x09],
+        [0x7daa, 0x0a],
+        [0x789d, 0x0b],
+        [0x662f, 0x0c],
+        [0x6318, 0x0d],
+        [0x6c41, 0x0e],
+        [0x6976, 0x0f],
+        [0x1689, 0x10],
+        [0x13be, 0x11],
+        [0x1ce7, 0x12],
+        [0x19d0, 0x13],
+        [0x0762, 0x14],
+        [0x0255, 0x15],
+        [0x0d0c, 0x16],
+        [0x083b, 0x17],
+        [0x355f, 0x18],
+        [0x3068, 0x19],
+        [0x3f31, 0x1a],
+        [0x3a06, 0x1b],
+        [0x24b4, 0x1c],
+        [0x2183, 0x1d],
+        [0x2eda, 0x1e],
+        [0x2bed, 0x1f],
+    ];
+
+    /**
+     * Offset i holds the number of 1 bits in the binary representation of i.
+     *
+     * @var int[]
+     */
+    private const BITS_SET_IN_HALF_BYTE = [0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4];
+
+    /**
+     * Error correction level.
+     *
+     * @var ErrorCorrectionLevel
+     */
+    private $ecLevel;
+
+    /**
+     * Data mask.
+     *
+     * @var int
+     */
+    private $dataMask;
+
+    protected function __construct(int $formatInfo)
+    {
+        $this->ecLevel = ErrorCorrectionLevel::forBits(($formatInfo >> 3) & 0x3);
+        $this->dataMask = $formatInfo & 0x7;
+    }
+
+    /**
+     * Checks how many bits are different between two integers.
+     */
+    public static function numBitsDiffering(int $a, int $b) : int
+    {
+        $a ^= $b;
+
+        return (
+            self::BITS_SET_IN_HALF_BYTE[$a & 0xf]
+            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 4) & 0xf)]
+            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 8) & 0xf)]
+            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 12) & 0xf)]
+            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 16) & 0xf)]
+            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 20) & 0xf)]
+            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 24) & 0xf)]
+            + self::BITS_SET_IN_HALF_BYTE[(BitUtils::unsignedRightShift($a, 28) & 0xf)]
+        );
+    }
+
+    /**
+     * Decodes format information.
+     */
+    public static function decodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
+    {
+        $formatInfo = self::doDecodeFormatInformation($maskedFormatInfo1, $maskedFormatInfo2);
+
+        if (null !== $formatInfo) {
+            return $formatInfo;
+        }
+
+        // Should return null, but, some QR codes apparently do not mask this info. Try again by actually masking the
+        // pattern first.
+        return self::doDecodeFormatInformation(
+            $maskedFormatInfo1 ^ self::FORMAT_INFO_MASK_QR,
+            $maskedFormatInfo2 ^ self::FORMAT_INFO_MASK_QR
+        );
+    }
+
+    /**
+     * Internal method for decoding format information.
+     */
+    private static function doDecodeFormatInformation(int $maskedFormatInfo1, int $maskedFormatInfo2) : ?self
+    {
+        $bestDifference = PHP_INT_MAX;
+        $bestFormatInfo = 0;
+
+        foreach (self::FORMAT_INFO_DECODE_LOOKUP as $decodeInfo) {
+            $targetInfo = $decodeInfo[0];
+
+            if ($targetInfo === $maskedFormatInfo1 || $targetInfo === $maskedFormatInfo2) {
+                // Found an exact match
+                return new self($decodeInfo[1]);
+            }
+
+            $bitsDifference = self::numBitsDiffering($maskedFormatInfo1, $targetInfo);
+
+            if ($bitsDifference < $bestDifference) {
+                $bestFormatInfo = $decodeInfo[1];
+                $bestDifference = $bitsDifference;
+            }
+
+            if ($maskedFormatInfo1 !== $maskedFormatInfo2) {
+                // Also try the other option
+                $bitsDifference = self::numBitsDiffering($maskedFormatInfo2, $targetInfo);
+
+                if ($bitsDifference < $bestDifference) {
+                    $bestFormatInfo = $decodeInfo[1];
+                    $bestDifference = $bitsDifference;
+                }
+            }
+        }
+
+        // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match.
+        if ($bestDifference <= 3) {
+            return new self($bestFormatInfo);
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the error correction level.
+     */
+    public function getErrorCorrectionLevel() : ErrorCorrectionLevel
+    {
+        return $this->ecLevel;
+    }
+
+    /**
+     * Returns the data mask.
+     */
+    public function getDataMask() : int
+    {
+        return $this->dataMask;
+    }
+
+    /**
+     * Hashes the code of the EC level.
+     */
+    public function hashCode() : int
+    {
+        return ($this->ecLevel->getBits() << 3) | $this->dataMask;
+    }
+
+    /**
+     * Verifies if this instance equals another one.
+     */
+    public function equals(self $other) : bool
+    {
+        return (
+            $this->ecLevel === $other->ecLevel
+            && $this->dataMask === $other->dataMask
+        );
+    }
+}

+ 79 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/Mode.php

@@ -0,0 +1,79 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+use DASPRiD\Enum\AbstractEnum;
+
+/**
+ * Enum representing various modes in which data can be encoded to bits.
+ *
+ * @method static self TERMINATOR()
+ * @method static self NUMERIC()
+ * @method static self ALPHANUMERIC()
+ * @method static self STRUCTURED_APPEND()
+ * @method static self BYTE()
+ * @method static self ECI()
+ * @method static self KANJI()
+ * @method static self FNC1_FIRST_POSITION()
+ * @method static self FNC1_SECOND_POSITION()
+ * @method static self HANZI()
+ */
+final class Mode extends AbstractEnum
+{
+    protected const TERMINATOR = [[0, 0, 0], 0x00];
+    protected const NUMERIC = [[10, 12, 14], 0x01];
+    protected const ALPHANUMERIC = [[9, 11, 13], 0x02];
+    protected const STRUCTURED_APPEND = [[0, 0, 0], 0x03];
+    protected const BYTE = [[8, 16, 16], 0x04];
+    protected const ECI = [[0, 0, 0], 0x07];
+    protected const KANJI = [[8, 10, 12], 0x08];
+    protected const FNC1_FIRST_POSITION = [[0, 0, 0], 0x05];
+    protected const FNC1_SECOND_POSITION = [[0, 0, 0], 0x09];
+    protected const HANZI = [[8, 10, 12], 0x0d];
+
+    /**
+     * @var int[]
+     */
+    private $characterCountBitsForVersions;
+
+    /**
+     * @var int
+     */
+    private $bits;
+
+    /**
+     * @param int[] $characterCountBitsForVersions
+     */
+    protected function __construct(array $characterCountBitsForVersions, int $bits)
+    {
+        $this->characterCountBitsForVersions = $characterCountBitsForVersions;
+        $this->bits = $bits;
+    }
+
+    /**
+     * Returns the number of bits used in a specific QR code version.
+     */
+    public function getCharacterCountBits(Version $version) : int
+    {
+        $number = $version->getVersionNumber();
+
+        if ($number <= 9) {
+            $offset = 0;
+        } elseif ($number <= 26) {
+            $offset = 1;
+        } else {
+            $offset = 2;
+        }
+
+        return $this->characterCountBitsForVersions[$offset];
+    }
+
+    /**
+     * Returns the four bits used to encode this mode.
+     */
+    public function getBits() : int
+    {
+        return $this->bits;
+    }
+}

+ 468 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/ReedSolomonCodec.php

@@ -0,0 +1,468 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+use BaconQrCode\Exception\InvalidArgumentException;
+use BaconQrCode\Exception\RuntimeException;
+use SplFixedArray;
+
+/**
+ * Reed-Solomon codec for 8-bit characters.
+ *
+ * Based on libfec by Phil Karn, KA9Q.
+ */
+final class ReedSolomonCodec
+{
+    /**
+     * Symbol size in bits.
+     *
+     * @var int
+     */
+    private $symbolSize;
+
+    /**
+     * Block size in symbols.
+     *
+     * @var int
+     */
+    private $blockSize;
+
+    /**
+     * First root of RS code generator polynomial, index form.
+     *
+     * @var int
+     */
+    private $firstRoot;
+
+    /**
+     * Primitive element to generate polynomial roots, index form.
+     *
+     * @var int
+     */
+    private $primitive;
+
+    /**
+     * Prim-th root of 1, index form.
+     *
+     * @var int
+     */
+    private $iPrimitive;
+
+    /**
+     * RS code generator polynomial degree (number of roots).
+     *
+     * @var int
+     */
+    private $numRoots;
+
+    /**
+     * Padding bytes at front of shortened block.
+     *
+     * @var int
+     */
+    private $padding;
+
+    /**
+     * Log lookup table.
+     *
+     * @var SplFixedArray
+     */
+    private $alphaTo;
+
+    /**
+     * Anti-Log lookup table.
+     *
+     * @var SplFixedArray
+     */
+    private $indexOf;
+
+    /**
+     * Generator polynomial.
+     *
+     * @var SplFixedArray
+     */
+    private $generatorPoly;
+
+    /**
+     * @throws InvalidArgumentException if symbol size ist not between 0 and 8
+     * @throws InvalidArgumentException if first root is invalid
+     * @throws InvalidArgumentException if num roots is invalid
+     * @throws InvalidArgumentException if padding is invalid
+     * @throws RuntimeException if field generator polynomial is not primitive
+     */
+    public function __construct(
+        int $symbolSize,
+        int $gfPoly,
+        int $firstRoot,
+        int $primitive,
+        int $numRoots,
+        int $padding
+    ) {
+        if ($symbolSize < 0 || $symbolSize > 8) {
+            throw new InvalidArgumentException('Symbol size must be between 0 and 8');
+        }
+
+        if ($firstRoot < 0 || $firstRoot >= (1 << $symbolSize)) {
+            throw new InvalidArgumentException('First root must be between 0 and ' . (1 << $symbolSize));
+        }
+
+        if ($numRoots < 0 || $numRoots >= (1 << $symbolSize)) {
+            throw new InvalidArgumentException('Num roots must be between 0 and ' . (1 << $symbolSize));
+        }
+
+        if ($padding < 0 || $padding >= ((1 << $symbolSize) - 1 - $numRoots)) {
+            throw new InvalidArgumentException(
+                'Padding must be between 0 and ' . ((1 << $symbolSize) - 1 - $numRoots)
+            );
+        }
+
+        $this->symbolSize = $symbolSize;
+        $this->blockSize = (1 << $symbolSize) - 1;
+        $this->padding = $padding;
+        $this->alphaTo = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
+        $this->indexOf = SplFixedArray::fromArray(array_fill(0, $this->blockSize + 1, 0), false);
+
+        // Generate galous field lookup table
+        $this->indexOf[0] = $this->blockSize;
+        $this->alphaTo[$this->blockSize] = 0;
+
+        $sr = 1;
+
+        for ($i = 0; $i < $this->blockSize; ++$i) {
+            $this->indexOf[$sr] = $i;
+            $this->alphaTo[$i]  = $sr;
+
+            $sr <<= 1;
+
+            if ($sr & (1 << $symbolSize)) {
+                $sr ^= $gfPoly;
+            }
+
+            $sr &= $this->blockSize;
+        }
+
+        if (1 !== $sr) {
+            throw new RuntimeException('Field generator polynomial is not primitive');
+        }
+
+        // Form RS code generator polynomial from its roots
+        $this->generatorPoly = SplFixedArray::fromArray(array_fill(0, $numRoots + 1, 0), false);
+        $this->firstRoot = $firstRoot;
+        $this->primitive = $primitive;
+        $this->numRoots = $numRoots;
+
+        // Find prim-th root of 1, used in decoding
+        for ($iPrimitive = 1; ($iPrimitive % $primitive) !== 0; $iPrimitive += $this->blockSize) {
+        }
+
+        $this->iPrimitive = intdiv($iPrimitive, $primitive);
+
+        $this->generatorPoly[0] = 1;
+
+        for ($i = 0, $root = $firstRoot * $primitive; $i < $numRoots; ++$i, $root += $primitive) {
+            $this->generatorPoly[$i + 1] = 1;
+
+            for ($j = $i; $j > 0; $j--) {
+                if ($this->generatorPoly[$j] !== 0) {
+                    $this->generatorPoly[$j] = $this->generatorPoly[$j - 1] ^ $this->alphaTo[
+                        $this->modNn($this->indexOf[$this->generatorPoly[$j]] + $root)
+                    ];
+                } else {
+                    $this->generatorPoly[$j] = $this->generatorPoly[$j - 1];
+                }
+            }
+
+            $this->generatorPoly[$j] = $this->alphaTo[$this->modNn($this->indexOf[$this->generatorPoly[0]] + $root)];
+        }
+
+        // Convert generator poly to index form for quicker encoding
+        for ($i = 0; $i <= $numRoots; ++$i) {
+            $this->generatorPoly[$i] = $this->indexOf[$this->generatorPoly[$i]];
+        }
+    }
+
+    /**
+     * Encodes data and writes result back into parity array.
+     */
+    public function encode(SplFixedArray $data, SplFixedArray $parity) : void
+    {
+        for ($i = 0; $i < $this->numRoots; ++$i) {
+            $parity[$i] = 0;
+        }
+
+        $iterations = $this->blockSize - $this->numRoots - $this->padding;
+
+        for ($i = 0; $i < $iterations; ++$i) {
+            $feedback = $this->indexOf[$data[$i] ^ $parity[0]];
+
+            if ($feedback !== $this->blockSize) {
+                // Feedback term is non-zero
+                $feedback = $this->modNn($this->blockSize - $this->generatorPoly[$this->numRoots] + $feedback);
+
+                for ($j = 1; $j < $this->numRoots; ++$j) {
+                    $parity[$j] = $parity[$j] ^ $this->alphaTo[
+                        $this->modNn($feedback + $this->generatorPoly[$this->numRoots - $j])
+                    ];
+                }
+            }
+
+            for ($j = 0; $j < $this->numRoots - 1; ++$j) {
+                $parity[$j] = $parity[$j + 1];
+            }
+
+            if ($feedback !== $this->blockSize) {
+                $parity[$this->numRoots - 1] = $this->alphaTo[$this->modNn($feedback + $this->generatorPoly[0])];
+            } else {
+                $parity[$this->numRoots - 1] = 0;
+            }
+        }
+    }
+
+    /**
+     * Decodes received data.
+     */
+    public function decode(SplFixedArray $data, SplFixedArray $erasures = null) : ?int
+    {
+        // This speeds up the initialization a bit.
+        $numRootsPlusOne = SplFixedArray::fromArray(array_fill(0, $this->numRoots + 1, 0), false);
+        $numRoots = SplFixedArray::fromArray(array_fill(0, $this->numRoots, 0), false);
+
+        $lambda = clone $numRootsPlusOne;
+        $b = clone $numRootsPlusOne;
+        $t = clone $numRootsPlusOne;
+        $omega = clone $numRootsPlusOne;
+        $root = clone $numRoots;
+        $loc = clone $numRoots;
+
+        $numErasures = (null !== $erasures ? count($erasures) : 0);
+
+        // Form the Syndromes; i.e., evaluate data(x) at roots of g(x)
+        $syndromes = SplFixedArray::fromArray(array_fill(0, $this->numRoots, $data[0]), false);
+
+        for ($i = 1; $i < $this->blockSize - $this->padding; ++$i) {
+            for ($j = 0; $j < $this->numRoots; ++$j) {
+                if ($syndromes[$j] === 0) {
+                    $syndromes[$j] = $data[$i];
+                } else {
+                    $syndromes[$j] = $data[$i] ^ $this->alphaTo[
+                        $this->modNn($this->indexOf[$syndromes[$j]] + ($this->firstRoot + $j) * $this->primitive)
+                    ];
+                }
+            }
+        }
+
+        // Convert syndromes to index form, checking for nonzero conditions
+        $syndromeError = 0;
+
+        for ($i = 0; $i < $this->numRoots; ++$i) {
+            $syndromeError |= $syndromes[$i];
+            $syndromes[$i] = $this->indexOf[$syndromes[$i]];
+        }
+
+        if (! $syndromeError) {
+            // If syndrome is zero, data[] is a codeword and there are no errors to correct, so return data[]
+            // unmodified.
+            return 0;
+        }
+
+        $lambda[0] = 1;
+
+        if ($numErasures > 0) {
+            // Init lambda to be the erasure locator polynomial
+            $lambda[1] = $this->alphaTo[$this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[0]))];
+
+            for ($i = 1; $i < $numErasures; ++$i) {
+                $u = $this->modNn($this->primitive * ($this->blockSize - 1 - $erasures[$i]));
+
+                for ($j = $i + 1; $j > 0; --$j) {
+                    $tmp = $this->indexOf[$lambda[$j - 1]];
+
+                    if ($tmp !== $this->blockSize) {
+                        $lambda[$j] = $lambda[$j] ^ $this->alphaTo[$this->modNn($u + $tmp)];
+                    }
+                }
+            }
+        }
+
+        for ($i = 0; $i <= $this->numRoots; ++$i) {
+            $b[$i] = $this->indexOf[$lambda[$i]];
+        }
+
+        // Begin Berlekamp-Massey algorithm to determine error+erasure locator polynomial
+        $r  = $numErasures;
+        $el = $numErasures;
+
+        while (++$r <= $this->numRoots) {
+            // Compute discrepancy at the r-th step in poly form
+            $discrepancyR = 0;
+
+            for ($i = 0; $i < $r; ++$i) {
+                if ($lambda[$i] !== 0 && $syndromes[$r - $i - 1] !== $this->blockSize) {
+                    $discrepancyR ^= $this->alphaTo[
+                        $this->modNn($this->indexOf[$lambda[$i]] + $syndromes[$r - $i - 1])
+                    ];
+                }
+            }
+
+            $discrepancyR = $this->indexOf[$discrepancyR];
+
+            if ($discrepancyR === $this->blockSize) {
+                $tmp = $b->toArray();
+                array_unshift($tmp, $this->blockSize);
+                array_pop($tmp);
+                $b = SplFixedArray::fromArray($tmp, false);
+                continue;
+            }
+
+            $t[0] = $lambda[0];
+
+            for ($i = 0; $i < $this->numRoots; ++$i) {
+                if ($b[$i] !== $this->blockSize) {
+                    $t[$i + 1] = $lambda[$i + 1] ^ $this->alphaTo[$this->modNn($discrepancyR + $b[$i])];
+                } else {
+                    $t[$i + 1] = $lambda[$i + 1];
+                }
+            }
+
+            if (2 * $el <= $r + $numErasures - 1) {
+                $el = $r + $numErasures - $el;
+
+                for ($i = 0; $i <= $this->numRoots; ++$i) {
+                    $b[$i] = (
+                        $lambda[$i] === 0
+                        ? $this->blockSize
+                        : $this->modNn($this->indexOf[$lambda[$i]] - $discrepancyR + $this->blockSize)
+                    );
+                }
+            } else {
+                $tmp = $b->toArray();
+                array_unshift($tmp, $this->blockSize);
+                array_pop($tmp);
+                $b = SplFixedArray::fromArray($tmp, false);
+            }
+
+            $lambda = clone $t;
+        }
+
+        // Convert lambda to index form and compute deg(lambda(x))
+        $degLambda = 0;
+
+        for ($i = 0; $i <= $this->numRoots; ++$i) {
+            $lambda[$i] = $this->indexOf[$lambda[$i]];
+
+            if ($lambda[$i] !== $this->blockSize) {
+                $degLambda = $i;
+            }
+        }
+
+        // Find roots of the error+erasure locator polynomial by Chien search.
+        $reg = clone $lambda;
+        $reg[0] = 0;
+        $count = 0;
+        $i = 1;
+
+        for ($k = $this->iPrimitive - 1; $i <= $this->blockSize; ++$i, $k = $this->modNn($k + $this->iPrimitive)) {
+            $q = 1;
+
+            for ($j = $degLambda; $j > 0; $j--) {
+                if ($reg[$j] !== $this->blockSize) {
+                    $reg[$j] = $this->modNn($reg[$j] + $j);
+                    $q ^= $this->alphaTo[$reg[$j]];
+                }
+            }
+
+            if ($q !== 0) {
+                // Not a root
+                continue;
+            }
+
+            // Store root (index-form) and error location number
+            $root[$count] = $i;
+            $loc[$count] = $k;
+
+            if (++$count === $degLambda) {
+                break;
+            }
+        }
+
+        if ($degLambda !== $count) {
+            // deg(lambda) unequal to number of roots: uncorrectable error detected
+            return null;
+        }
+
+        // Compute err+eras evaluate poly omega(x) = s(x)*lambda(x) (modulo x**numRoots). In index form. Also find
+        // deg(omega).
+        $degOmega = $degLambda - 1;
+
+        for ($i = 0; $i <= $degOmega; ++$i) {
+            $tmp = 0;
+
+            for ($j = $i; $j >= 0; --$j) {
+                if ($syndromes[$i - $j] !== $this->blockSize && $lambda[$j] !== $this->blockSize) {
+                    $tmp ^= $this->alphaTo[$this->modNn($syndromes[$i - $j] + $lambda[$j])];
+                }
+            }
+
+            $omega[$i] = $this->indexOf[$tmp];
+        }
+
+        // Compute error values in poly-form. num1 = omega(inv(X(l))), num2 = inv(X(l))**(firstRoot-1) and
+        // den = lambda_pr(inv(X(l))) all in poly form.
+        for ($j = $count - 1; $j >= 0; --$j) {
+            $num1 = 0;
+
+            for ($i = $degOmega; $i >= 0; $i--) {
+                if ($omega[$i] !== $this->blockSize) {
+                    $num1 ^= $this->alphaTo[$this->modNn($omega[$i] + $i * $root[$j])];
+                }
+            }
+
+            $num2 = $this->alphaTo[$this->modNn($root[$j] * ($this->firstRoot - 1) + $this->blockSize)];
+            $den  = 0;
+
+            // lambda[i+1] for i even is the formal derivativelambda_pr of lambda[i]
+            for ($i = min($degLambda, $this->numRoots - 1) & ~1; $i >= 0; $i -= 2) {
+                if ($lambda[$i + 1] !== $this->blockSize) {
+                    $den ^= $this->alphaTo[$this->modNn($lambda[$i + 1] + $i * $root[$j])];
+                }
+            }
+
+            // Apply error to data
+            if ($num1 !== 0 && $loc[$j] >= $this->padding) {
+                $data[$loc[$j] - $this->padding] = $data[$loc[$j] - $this->padding] ^ (
+                    $this->alphaTo[
+                        $this->modNn(
+                            $this->indexOf[$num1] + $this->indexOf[$num2] + $this->blockSize - $this->indexOf[$den]
+                        )
+                    ]
+                );
+            }
+        }
+
+        if (null !== $erasures) {
+            if (count($erasures) < $count) {
+                $erasures->setSize($count);
+            }
+
+            for ($i = 0; $i < $count; $i++) {
+                $erasures[$i] = $loc[$i];
+            }
+        }
+
+        return $count;
+    }
+
+    /**
+     * Computes $x % GF_SIZE, where GF_SIZE is 2**GF_BITS - 1, without a slow divide.
+     */
+    private function modNn(int $x) : int
+    {
+        while ($x >= $this->blockSize) {
+            $x -= $this->blockSize;
+            $x = ($x >> $this->symbolSize) + ($x & $this->blockSize);
+        }
+
+        return $x;
+    }
+}

+ 596 - 0
htdocs/includes/bacon/bacon-qr-code/src/Common/Version.php

@@ -0,0 +1,596 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Common;
+
+use BaconQrCode\Exception\InvalidArgumentException;
+use SplFixedArray;
+
+/**
+ * Version representation.
+ */
+final class Version
+{
+    private const VERSION_DECODE_INFO = [
+        0x07c94,
+        0x085bc,
+        0x09a99,
+        0x0a4d3,
+        0x0bbf6,
+        0x0c762,
+        0x0d847,
+        0x0e60d,
+        0x0f928,
+        0x10b78,
+        0x1145d,
+        0x12a17,
+        0x13532,
+        0x149a6,
+        0x15683,
+        0x168c9,
+        0x177ec,
+        0x18ec4,
+        0x191e1,
+        0x1afab,
+        0x1b08e,
+        0x1cc1a,
+        0x1d33f,
+        0x1ed75,
+        0x1f250,
+        0x209d5,
+        0x216f0,
+        0x228ba,
+        0x2379f,
+        0x24b0b,
+        0x2542e,
+        0x26a64,
+        0x27541,
+        0x28c69,
+    ];
+
+    /**
+     * Version number of this version.
+     *
+     * @var int
+     */
+    private $versionNumber;
+
+    /**
+     * Alignment pattern centers.
+     *
+     * @var SplFixedArray
+     */
+    private $alignmentPatternCenters;
+
+    /**
+     * Error correction blocks.
+     *
+     * @var EcBlocks[]
+     */
+    private $ecBlocks;
+
+    /**
+     * Total number of codewords.
+     *
+     * @var int
+     */
+    private $totalCodewords;
+
+    /**
+     * Cached version instances.
+     *
+     * @var array<int, self>|null
+     */
+    private static $versions;
+
+    /**
+     * @param int[] $alignmentPatternCenters
+     */
+    private function __construct(
+        int $versionNumber,
+        array $alignmentPatternCenters,
+        EcBlocks ...$ecBlocks
+    ) {
+        $this->versionNumber = $versionNumber;
+        $this->alignmentPatternCenters = $alignmentPatternCenters;
+        $this->ecBlocks = $ecBlocks;
+
+        $totalCodewords = 0;
+        $ecCodewords = $ecBlocks[0]->getEcCodewordsPerBlock();
+
+        foreach ($ecBlocks[0]->getEcBlocks() as $ecBlock) {
+            $totalCodewords += $ecBlock->getCount() * ($ecBlock->getDataCodewords() + $ecCodewords);
+        }
+
+        $this->totalCodewords = $totalCodewords;
+    }
+
+    /**
+     * Returns the version number.
+     */
+    public function getVersionNumber() : int
+    {
+        return $this->versionNumber;
+    }
+
+    /**
+     * Returns the alignment pattern centers.
+     *
+     * @return int[]
+     */
+    public function getAlignmentPatternCenters() : array
+    {
+        return $this->alignmentPatternCenters;
+    }
+
+    /**
+     * Returns the total number of codewords.
+     */
+    public function getTotalCodewords() : int
+    {
+        return $this->totalCodewords;
+    }
+
+    /**
+     * Calculates the dimension for the current version.
+     */
+    public function getDimensionForVersion() : int
+    {
+        return 17 + 4 * $this->versionNumber;
+    }
+
+    /**
+     * Returns the number of EC blocks for a specific EC level.
+     */
+    public function getEcBlocksForLevel(ErrorCorrectionLevel $ecLevel) : EcBlocks
+    {
+        return $this->ecBlocks[$ecLevel->ordinal()];
+    }
+
+    /**
+     * Gets a provisional version number for a specific dimension.
+     *
+     * @throws InvalidArgumentException if dimension is not 1 mod 4
+     */
+    public static function getProvisionalVersionForDimension(int $dimension) : self
+    {
+        if (1 !== $dimension % 4) {
+            throw new InvalidArgumentException('Dimension is not 1 mod 4');
+        }
+
+        return self::getVersionForNumber(intdiv($dimension - 17, 4));
+    }
+
+    /**
+     * Gets a version instance for a specific version number.
+     *
+     * @throws InvalidArgumentException if version number is out of range
+     */
+    public static function getVersionForNumber(int $versionNumber) : self
+    {
+        if ($versionNumber < 1 || $versionNumber > 40) {
+            throw new InvalidArgumentException('Version number must be between 1 and 40');
+        }
+
+        return self::versions()[$versionNumber - 1];
+    }
+
+    /**
+     * Decodes version information from an integer and returns the version.
+     */
+    public static function decodeVersionInformation(int $versionBits) : ?self
+    {
+        $bestDifference = PHP_INT_MAX;
+        $bestVersion = 0;
+
+        foreach (self::VERSION_DECODE_INFO as $i => $targetVersion) {
+            if ($targetVersion === $versionBits) {
+                return self::getVersionForNumber($i + 7);
+            }
+
+            $bitsDifference = FormatInformation::numBitsDiffering($versionBits, $targetVersion);
+
+            if ($bitsDifference < $bestDifference) {
+                $bestVersion = $i + 7;
+                $bestDifference = $bitsDifference;
+            }
+        }
+
+        if ($bestDifference <= 3) {
+            return self::getVersionForNumber($bestVersion);
+        }
+
+        return null;
+    }
+
+    /**
+     * Builds the function pattern for the current version.
+     */
+    public function buildFunctionPattern() : BitMatrix
+    {
+        $dimension = $this->getDimensionForVersion();
+        $bitMatrix = new BitMatrix($dimension);
+
+        // Top left finder pattern + separator + format
+        $bitMatrix->setRegion(0, 0, 9, 9);
+        // Top right finder pattern + separator + format
+        $bitMatrix->setRegion($dimension - 8, 0, 8, 9);
+        // Bottom left finder pattern + separator + format
+        $bitMatrix->setRegion(0, $dimension - 8, 9, 8);
+
+        // Alignment patterns
+        $max = count($this->alignmentPatternCenters);
+
+        for ($x = 0; $x < $max; ++$x) {
+            $i = $this->alignmentPatternCenters[$x] - 2;
+
+            for ($y = 0; $y < $max; ++$y) {
+                if (($x === 0 && ($y === 0 || $y === $max - 1)) || ($x === $max - 1 && $y === 0)) {
+                    // No alignment patterns near the three finder paterns
+                    continue;
+                }
+
+                $bitMatrix->setRegion($this->alignmentPatternCenters[$y] - 2, $i, 5, 5);
+            }
+        }
+
+        // Vertical timing pattern
+        $bitMatrix->setRegion(6, 9, 1, $dimension - 17);
+        // Horizontal timing pattern
+        $bitMatrix->setRegion(9, 6, $dimension - 17, 1);
+
+        if ($this->versionNumber > 6) {
+            // Version info, top right
+            $bitMatrix->setRegion($dimension - 11, 0, 3, 6);
+            // Version info, bottom left
+            $bitMatrix->setRegion(0, $dimension - 11, 6, 3);
+        }
+
+        return $bitMatrix;
+    }
+
+    /**
+     * Returns a string representation for the version.
+     */
+    public function __toString() : string
+    {
+        return (string) $this->versionNumber;
+    }
+
+    /**
+     * Build and cache a specific version.
+     *
+     * See ISO 18004:2006 6.5.1 Table 9.
+     *
+     * @return array<int, self>
+     */
+    private static function versions() : array
+    {
+        if (null !== self::$versions) {
+            return self::$versions;
+        }
+
+        return self::$versions = [
+            new self(
+                1,
+                [],
+                new EcBlocks(7, new EcBlock(1, 19)),
+                new EcBlocks(10, new EcBlock(1, 16)),
+                new EcBlocks(13, new EcBlock(1, 13)),
+                new EcBlocks(17, new EcBlock(1, 9))
+            ),
+            new self(
+                2,
+                [6, 18],
+                new EcBlocks(10, new EcBlock(1, 34)),
+                new EcBlocks(16, new EcBlock(1, 28)),
+                new EcBlocks(22, new EcBlock(1, 22)),
+                new EcBlocks(28, new EcBlock(1, 16))
+            ),
+            new self(
+                3,
+                [6, 22],
+                new EcBlocks(15, new EcBlock(1, 55)),
+                new EcBlocks(26, new EcBlock(1, 44)),
+                new EcBlocks(18, new EcBlock(2, 17)),
+                new EcBlocks(22, new EcBlock(2, 13))
+            ),
+            new self(
+                4,
+                [6, 26],
+                new EcBlocks(20, new EcBlock(1, 80)),
+                new EcBlocks(18, new EcBlock(2, 32)),
+                new EcBlocks(26, new EcBlock(3, 24)),
+                new EcBlocks(16, new EcBlock(4, 9))
+            ),
+            new self(
+                5,
+                [6, 30],
+                new EcBlocks(26, new EcBlock(1, 108)),
+                new EcBlocks(24, new EcBlock(2, 43)),
+                new EcBlocks(18, new EcBlock(2, 15), new EcBlock(2, 16)),
+                new EcBlocks(22, new EcBlock(2, 11), new EcBlock(2, 12))
+            ),
+            new self(
+                6,
+                [6, 34],
+                new EcBlocks(18, new EcBlock(2, 68)),
+                new EcBlocks(16, new EcBlock(4, 27)),
+                new EcBlocks(24, new EcBlock(4, 19)),
+                new EcBlocks(28, new EcBlock(4, 15))
+            ),
+            new self(
+                7,
+                [6, 22, 38],
+                new EcBlocks(20, new EcBlock(2, 78)),
+                new EcBlocks(18, new EcBlock(4, 31)),
+                new EcBlocks(18, new EcBlock(2, 14), new EcBlock(4, 15)),
+                new EcBlocks(26, new EcBlock(4, 13), new EcBlock(1, 14))
+            ),
+            new self(
+                8,
+                [6, 24, 42],
+                new EcBlocks(24, new EcBlock(2, 97)),
+                new EcBlocks(22, new EcBlock(2, 38), new EcBlock(2, 39)),
+                new EcBlocks(22, new EcBlock(4, 18), new EcBlock(2, 19)),
+                new EcBlocks(26, new EcBlock(4, 14), new EcBlock(2, 15))
+            ),
+            new self(
+                9,
+                [6, 26, 46],
+                new EcBlocks(30, new EcBlock(2, 116)),
+                new EcBlocks(22, new EcBlock(3, 36), new EcBlock(2, 37)),
+                new EcBlocks(20, new EcBlock(4, 16), new EcBlock(4, 17)),
+                new EcBlocks(24, new EcBlock(4, 12), new EcBlock(4, 13))
+            ),
+            new self(
+                10,
+                [6, 28, 50],
+                new EcBlocks(18, new EcBlock(2, 68), new EcBlock(2, 69)),
+                new EcBlocks(26, new EcBlock(4, 43), new EcBlock(1, 44)),
+                new EcBlocks(24, new EcBlock(6, 19), new EcBlock(2, 20)),
+                new EcBlocks(28, new EcBlock(6, 15), new EcBlock(2, 16))
+            ),
+            new self(
+                11,
+                [6, 30, 54],
+                new EcBlocks(20, new EcBlock(4, 81)),
+                new EcBlocks(30, new EcBlock(1, 50), new EcBlock(4, 51)),
+                new EcBlocks(28, new EcBlock(4, 22), new EcBlock(4, 23)),
+                new EcBlocks(24, new EcBlock(3, 12), new EcBlock(8, 13))
+            ),
+            new self(
+                12,
+                [6, 32, 58],
+                new EcBlocks(24, new EcBlock(2, 92), new EcBlock(2, 93)),
+                new EcBlocks(22, new EcBlock(6, 36), new EcBlock(2, 37)),
+                new EcBlocks(26, new EcBlock(4, 20), new EcBlock(6, 21)),
+                new EcBlocks(28, new EcBlock(7, 14), new EcBlock(4, 15))
+            ),
+            new self(
+                13,
+                [6, 34, 62],
+                new EcBlocks(26, new EcBlock(4, 107)),
+                new EcBlocks(22, new EcBlock(8, 37), new EcBlock(1, 38)),
+                new EcBlocks(24, new EcBlock(8, 20), new EcBlock(4, 21)),
+                new EcBlocks(22, new EcBlock(12, 11), new EcBlock(4, 12))
+            ),
+            new self(
+                14,
+                [6, 26, 46, 66],
+                new EcBlocks(30, new EcBlock(3, 115), new EcBlock(1, 116)),
+                new EcBlocks(24, new EcBlock(4, 40), new EcBlock(5, 41)),
+                new EcBlocks(20, new EcBlock(11, 16), new EcBlock(5, 17)),
+                new EcBlocks(24, new EcBlock(11, 12), new EcBlock(5, 13))
+            ),
+            new self(
+                15,
+                [6, 26, 48, 70],
+                new EcBlocks(22, new EcBlock(5, 87), new EcBlock(1, 88)),
+                new EcBlocks(24, new EcBlock(5, 41), new EcBlock(5, 42)),
+                new EcBlocks(30, new EcBlock(5, 24), new EcBlock(7, 25)),
+                new EcBlocks(24, new EcBlock(11, 12), new EcBlock(7, 13))
+            ),
+            new self(
+                16,
+                [6, 26, 50, 74],
+                new EcBlocks(24, new EcBlock(5, 98), new EcBlock(1, 99)),
+                new EcBlocks(28, new EcBlock(7, 45), new EcBlock(3, 46)),
+                new EcBlocks(24, new EcBlock(15, 19), new EcBlock(2, 20)),
+                new EcBlocks(30, new EcBlock(3, 15), new EcBlock(13, 16))
+            ),
+            new self(
+                17,
+                [6, 30, 54, 78],
+                new EcBlocks(28, new EcBlock(1, 107), new EcBlock(5, 108)),
+                new EcBlocks(28, new EcBlock(10, 46), new EcBlock(1, 47)),
+                new EcBlocks(28, new EcBlock(1, 22), new EcBlock(15, 23)),
+                new EcBlocks(28, new EcBlock(2, 14), new EcBlock(17, 15))
+            ),
+            new self(
+                18,
+                [6, 30, 56, 82],
+                new EcBlocks(30, new EcBlock(5, 120), new EcBlock(1, 121)),
+                new EcBlocks(26, new EcBlock(9, 43), new EcBlock(4, 44)),
+                new EcBlocks(28, new EcBlock(17, 22), new EcBlock(1, 23)),
+                new EcBlocks(28, new EcBlock(2, 14), new EcBlock(19, 15))
+            ),
+            new self(
+                19,
+                [6, 30, 58, 86],
+                new EcBlocks(28, new EcBlock(3, 113), new EcBlock(4, 114)),
+                new EcBlocks(26, new EcBlock(3, 44), new EcBlock(11, 45)),
+                new EcBlocks(26, new EcBlock(17, 21), new EcBlock(4, 22)),
+                new EcBlocks(26, new EcBlock(9, 13), new EcBlock(16, 14))
+            ),
+            new self(
+                20,
+                [6, 34, 62, 90],
+                new EcBlocks(28, new EcBlock(3, 107), new EcBlock(5, 108)),
+                new EcBlocks(26, new EcBlock(3, 41), new EcBlock(13, 42)),
+                new EcBlocks(30, new EcBlock(15, 24), new EcBlock(5, 25)),
+                new EcBlocks(28, new EcBlock(15, 15), new EcBlock(10, 16))
+            ),
+            new self(
+                21,
+                [6, 28, 50, 72, 94],
+                new EcBlocks(28, new EcBlock(4, 116), new EcBlock(4, 117)),
+                new EcBlocks(26, new EcBlock(17, 42)),
+                new EcBlocks(28, new EcBlock(17, 22), new EcBlock(6, 23)),
+                new EcBlocks(30, new EcBlock(19, 16), new EcBlock(6, 17))
+            ),
+            new self(
+                22,
+                [6, 26, 50, 74, 98],
+                new EcBlocks(28, new EcBlock(2, 111), new EcBlock(7, 112)),
+                new EcBlocks(28, new EcBlock(17, 46)),
+                new EcBlocks(30, new EcBlock(7, 24), new EcBlock(16, 25)),
+                new EcBlocks(24, new EcBlock(34, 13))
+            ),
+            new self(
+                23,
+                [6, 30, 54, 78, 102],
+                new EcBlocks(30, new EcBlock(4, 121), new EcBlock(5, 122)),
+                new EcBlocks(28, new EcBlock(4, 47), new EcBlock(14, 48)),
+                new EcBlocks(30, new EcBlock(11, 24), new EcBlock(14, 25)),
+                new EcBlocks(30, new EcBlock(16, 15), new EcBlock(14, 16))
+            ),
+            new self(
+                24,
+                [6, 28, 54, 80, 106],
+                new EcBlocks(30, new EcBlock(6, 117), new EcBlock(4, 118)),
+                new EcBlocks(28, new EcBlock(6, 45), new EcBlock(14, 46)),
+                new EcBlocks(30, new EcBlock(11, 24), new EcBlock(16, 25)),
+                new EcBlocks(30, new EcBlock(30, 16), new EcBlock(2, 17))
+            ),
+            new self(
+                25,
+                [6, 32, 58, 84, 110],
+                new EcBlocks(26, new EcBlock(8, 106), new EcBlock(4, 107)),
+                new EcBlocks(28, new EcBlock(8, 47), new EcBlock(13, 48)),
+                new EcBlocks(30, new EcBlock(7, 24), new EcBlock(22, 25)),
+                new EcBlocks(30, new EcBlock(22, 15), new EcBlock(13, 16))
+            ),
+            new self(
+                26,
+                [6, 30, 58, 86, 114],
+                new EcBlocks(28, new EcBlock(10, 114), new EcBlock(2, 115)),
+                new EcBlocks(28, new EcBlock(19, 46), new EcBlock(4, 47)),
+                new EcBlocks(28, new EcBlock(28, 22), new EcBlock(6, 23)),
+                new EcBlocks(30, new EcBlock(33, 16), new EcBlock(4, 17))
+            ),
+            new self(
+                27,
+                [6, 34, 62, 90, 118],
+                new EcBlocks(30, new EcBlock(8, 122), new EcBlock(4, 123)),
+                new EcBlocks(28, new EcBlock(22, 45), new EcBlock(3, 46)),
+                new EcBlocks(30, new EcBlock(8, 23), new EcBlock(26, 24)),
+                new EcBlocks(30, new EcBlock(12, 15), new EcBlock(28, 16))
+            ),
+            new self(
+                28,
+                [6, 26, 50, 74, 98, 122],
+                new EcBlocks(30, new EcBlock(3, 117), new EcBlock(10, 118)),
+                new EcBlocks(28, new EcBlock(3, 45), new EcBlock(23, 46)),
+                new EcBlocks(30, new EcBlock(4, 24), new EcBlock(31, 25)),
+                new EcBlocks(30, new EcBlock(11, 15), new EcBlock(31, 16))
+            ),
+            new self(
+                29,
+                [6, 30, 54, 78, 102, 126],
+                new EcBlocks(30, new EcBlock(7, 116), new EcBlock(7, 117)),
+                new EcBlocks(28, new EcBlock(21, 45), new EcBlock(7, 46)),
+                new EcBlocks(30, new EcBlock(1, 23), new EcBlock(37, 24)),
+                new EcBlocks(30, new EcBlock(19, 15), new EcBlock(26, 16))
+            ),
+            new self(
+                30,
+                [6, 26, 52, 78, 104, 130],
+                new EcBlocks(30, new EcBlock(5, 115), new EcBlock(10, 116)),
+                new EcBlocks(28, new EcBlock(19, 47), new EcBlock(10, 48)),
+                new EcBlocks(30, new EcBlock(15, 24), new EcBlock(25, 25)),
+                new EcBlocks(30, new EcBlock(23, 15), new EcBlock(25, 16))
+            ),
+            new self(
+                31,
+                [6, 30, 56, 82, 108, 134],
+                new EcBlocks(30, new EcBlock(13, 115), new EcBlock(3, 116)),
+                new EcBlocks(28, new EcBlock(2, 46), new EcBlock(29, 47)),
+                new EcBlocks(30, new EcBlock(42, 24), new EcBlock(1, 25)),
+                new EcBlocks(30, new EcBlock(23, 15), new EcBlock(28, 16))
+            ),
+            new self(
+                32,
+                [6, 34, 60, 86, 112, 138],
+                new EcBlocks(30, new EcBlock(17, 115)),
+                new EcBlocks(28, new EcBlock(10, 46), new EcBlock(23, 47)),
+                new EcBlocks(30, new EcBlock(10, 24), new EcBlock(35, 25)),
+                new EcBlocks(30, new EcBlock(19, 15), new EcBlock(35, 16))
+            ),
+            new self(
+                33,
+                [6, 30, 58, 86, 114, 142],
+                new EcBlocks(30, new EcBlock(17, 115), new EcBlock(1, 116)),
+                new EcBlocks(28, new EcBlock(14, 46), new EcBlock(21, 47)),
+                new EcBlocks(30, new EcBlock(29, 24), new EcBlock(19, 25)),
+                new EcBlocks(30, new EcBlock(11, 15), new EcBlock(46, 16))
+            ),
+            new self(
+                34,
+                [6, 34, 62, 90, 118, 146],
+                new EcBlocks(30, new EcBlock(13, 115), new EcBlock(6, 116)),
+                new EcBlocks(28, new EcBlock(14, 46), new EcBlock(23, 47)),
+                new EcBlocks(30, new EcBlock(44, 24), new EcBlock(7, 25)),
+                new EcBlocks(30, new EcBlock(59, 16), new EcBlock(1, 17))
+            ),
+            new self(
+                35,
+                [6, 30, 54, 78, 102, 126, 150],
+                new EcBlocks(30, new EcBlock(12, 121), new EcBlock(7, 122)),
+                new EcBlocks(28, new EcBlock(12, 47), new EcBlock(26, 48)),
+                new EcBlocks(30, new EcBlock(39, 24), new EcBlock(14, 25)),
+                new EcBlocks(30, new EcBlock(22, 15), new EcBlock(41, 16))
+            ),
+            new self(
+                36,
+                [6, 24, 50, 76, 102, 128, 154],
+                new EcBlocks(30, new EcBlock(6, 121), new EcBlock(14, 122)),
+                new EcBlocks(28, new EcBlock(6, 47), new EcBlock(34, 48)),
+                new EcBlocks(30, new EcBlock(46, 24), new EcBlock(10, 25)),
+                new EcBlocks(30, new EcBlock(2, 15), new EcBlock(64, 16))
+            ),
+            new self(
+                37,
+                [6, 28, 54, 80, 106, 132, 158],
+                new EcBlocks(30, new EcBlock(17, 122), new EcBlock(4, 123)),
+                new EcBlocks(28, new EcBlock(29, 46), new EcBlock(14, 47)),
+                new EcBlocks(30, new EcBlock(49, 24), new EcBlock(10, 25)),
+                new EcBlocks(30, new EcBlock(24, 15), new EcBlock(46, 16))
+            ),
+            new self(
+                38,
+                [6, 32, 58, 84, 110, 136, 162],
+                new EcBlocks(30, new EcBlock(4, 122), new EcBlock(18, 123)),
+                new EcBlocks(28, new EcBlock(13, 46), new EcBlock(32, 47)),
+                new EcBlocks(30, new EcBlock(48, 24), new EcBlock(14, 25)),
+                new EcBlocks(30, new EcBlock(42, 15), new EcBlock(32, 16))
+            ),
+            new self(
+                39,
+                [6, 26, 54, 82, 110, 138, 166],
+                new EcBlocks(30, new EcBlock(20, 117), new EcBlock(4, 118)),
+                new EcBlocks(28, new EcBlock(40, 47), new EcBlock(7, 48)),
+                new EcBlocks(30, new EcBlock(43, 24), new EcBlock(22, 25)),
+                new EcBlocks(30, new EcBlock(10, 15), new EcBlock(67, 16))
+            ),
+            new self(
+                40,
+                [6, 30, 58, 86, 114, 142, 170],
+                new EcBlocks(30, new EcBlock(19, 118), new EcBlock(6, 119)),
+                new EcBlocks(28, new EcBlock(18, 47), new EcBlock(31, 48)),
+                new EcBlocks(30, new EcBlock(34, 24), new EcBlock(34, 25)),
+                new EcBlocks(30, new EcBlock(20, 15), new EcBlock(61, 16))
+            ),
+        ];
+    }
+}

+ 58 - 0
htdocs/includes/bacon/bacon-qr-code/src/Encoder/BlockPair.php

@@ -0,0 +1,58 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Encoder;
+
+use SplFixedArray;
+
+/**
+ * Block pair.
+ */
+final class BlockPair
+{
+    /**
+     * Data bytes in the block.
+     *
+     * @var SplFixedArray<int>
+     */
+    private $dataBytes;
+
+    /**
+     * Error correction bytes in the block.
+     *
+     * @var SplFixedArray<int>
+     */
+    private $errorCorrectionBytes;
+
+    /**
+     * Creates a new block pair.
+     *
+     * @param SplFixedArray<int> $data
+     * @param SplFixedArray<int> $errorCorrection
+     */
+    public function __construct(SplFixedArray $data, SplFixedArray $errorCorrection)
+    {
+        $this->dataBytes = $data;
+        $this->errorCorrectionBytes = $errorCorrection;
+    }
+
+    /**
+     * Gets the data bytes.
+     *
+     * @return SplFixedArray<int>
+     */
+    public function getDataBytes() : SplFixedArray
+    {
+        return $this->dataBytes;
+    }
+
+    /**
+     * Gets the error correction bytes.
+     *
+     * @return SplFixedArray<int>
+     */
+    public function getErrorCorrectionBytes() : SplFixedArray
+    {
+        return $this->errorCorrectionBytes;
+    }
+}

+ 150 - 0
htdocs/includes/bacon/bacon-qr-code/src/Encoder/ByteMatrix.php

@@ -0,0 +1,150 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Encoder;
+
+use SplFixedArray;
+use Traversable;
+
+/**
+ * Byte matrix.
+ */
+final class ByteMatrix
+{
+    /**
+     * Bytes in the matrix, represented as array.
+     *
+     * @var SplFixedArray<SplFixedArray<int>>
+     */
+    private $bytes;
+
+    /**
+     * Width of the matrix.
+     *
+     * @var int
+     */
+    private $width;
+
+    /**
+     * Height of the matrix.
+     *
+     * @var int
+     */
+    private $height;
+
+    public function __construct(int $width, int $height)
+    {
+        $this->height = $height;
+        $this->width = $width;
+        $this->bytes = new SplFixedArray($height);
+
+        for ($y = 0; $y < $height; ++$y) {
+            $this->bytes[$y] = SplFixedArray::fromArray(array_fill(0, $width, 0));
+        }
+    }
+
+    /**
+     * Gets the width of the matrix.
+     */
+    public function getWidth() : int
+    {
+        return $this->width;
+    }
+
+    /**
+     * Gets the height of the matrix.
+     */
+    public function getHeight() : int
+    {
+        return $this->height;
+    }
+
+    /**
+     * Gets the internal representation of the matrix.
+     *
+     * @return SplFixedArray<SplFixedArray<int>>
+     */
+    public function getArray() : SplFixedArray
+    {
+        return $this->bytes;
+    }
+
+    /**
+     * @return Traversable<int>
+     */
+    public function getBytes() : Traversable
+    {
+        foreach ($this->bytes as $row) {
+            foreach ($row as $byte) {
+                yield $byte;
+            }
+        }
+    }
+
+    /**
+     * Gets the byte for a specific position.
+     */
+    public function get(int $x, int $y) : int
+    {
+        return $this->bytes[$y][$x];
+    }
+
+    /**
+     * Sets the byte for a specific position.
+     */
+    public function set(int $x, int $y, int $value) : void
+    {
+        $this->bytes[$y][$x] = $value;
+    }
+
+    /**
+     * Clears the matrix with a specific value.
+     */
+    public function clear(int $value) : void
+    {
+        for ($y = 0; $y < $this->height; ++$y) {
+            for ($x = 0; $x < $this->width; ++$x) {
+                $this->bytes[$y][$x] = $value;
+            }
+        }
+    }
+
+    public function __clone()
+    {
+        $this->bytes = clone $this->bytes;
+
+        foreach ($this->bytes as $index => $row) {
+            $this->bytes[$index] = clone $row;
+        }
+    }
+
+    /**
+     * Returns a string representation of the matrix.
+     */
+    public function __toString() : string
+    {
+        $result = '';
+
+        for ($y = 0; $y < $this->height; $y++) {
+            for ($x = 0; $x < $this->width; $x++) {
+                switch ($this->bytes[$y][$x]) {
+                    case 0:
+                        $result .= ' 0';
+                        break;
+
+                    case 1:
+                        $result .= ' 1';
+                        break;
+
+                    default:
+                        $result .= '  ';
+                        break;
+                }
+            }
+
+            $result .= "\n";
+        }
+
+        return $result;
+    }
+}

+ 668 - 0
htdocs/includes/bacon/bacon-qr-code/src/Encoder/Encoder.php

@@ -0,0 +1,668 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Encoder;
+
+use BaconQrCode\Common\BitArray;
+use BaconQrCode\Common\CharacterSetEci;
+use BaconQrCode\Common\ErrorCorrectionLevel;
+use BaconQrCode\Common\Mode;
+use BaconQrCode\Common\ReedSolomonCodec;
+use BaconQrCode\Common\Version;
+use BaconQrCode\Exception\WriterException;
+use SplFixedArray;
+
+/**
+ * Encoder.
+ */
+final class Encoder
+{
+    /**
+     * Default byte encoding.
+     */
+    public const DEFAULT_BYTE_MODE_ECODING = 'ISO-8859-1';
+
+    /**
+     * The original table is defined in the table 5 of JISX0510:2004 (p.19).
+     */
+    private const ALPHANUMERIC_TABLE = [
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x00-0x0f
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // 0x10-0x1f
+        36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43,  // 0x20-0x2f
+        0,   1,  2,  3,  4,  5,  6,  7,  8,  9, 44, -1, -1, -1, -1, -1,  // 0x30-0x3f
+        -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,  // 0x40-0x4f
+        25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1,  // 0x50-0x5f
+    ];
+
+    /**
+     * Codec cache.
+     *
+     * @var array<string,ReedSolomonCodec>
+     */
+    private static $codecs = [];
+
+    /**
+     * Encodes "content" with the error correction level "ecLevel".
+     */
+    public static function encode(
+        string $content,
+        ErrorCorrectionLevel $ecLevel,
+        string $encoding = self::DEFAULT_BYTE_MODE_ECODING,
+        ?Version $forcedVersion = null
+    ) : QrCode {
+        // Pick an encoding mode appropriate for the content. Note that this
+        // will not attempt to use multiple modes / segments even if that were
+        // more efficient. Would be nice.
+        $mode = self::chooseMode($content, $encoding);
+
+        // This will store the header information, like mode and length, as well
+        // as "header" segments like an ECI segment.
+        $headerBits = new BitArray();
+
+        // Append ECI segment if applicable
+        if (Mode::BYTE() === $mode && self::DEFAULT_BYTE_MODE_ECODING !== $encoding) {
+            $eci = CharacterSetEci::getCharacterSetEciByName($encoding);
+
+            if (null !== $eci) {
+                self::appendEci($eci, $headerBits);
+            }
+        }
+
+        // (With ECI in place,) Write the mode marker
+        self::appendModeInfo($mode, $headerBits);
+
+        // Collect data within the main segment, separately, to count its size
+        // if needed. Don't add it to main payload yet.
+        $dataBits = new BitArray();
+        self::appendBytes($content, $mode, $dataBits, $encoding);
+
+        // Hard part: need to know version to know how many bits length takes.
+        // But need to know how many bits it takes to know version. First we
+        // take a guess at version by assuming version will be the minimum, 1:
+        $provisionalBitsNeeded = $headerBits->getSize()
+            + $mode->getCharacterCountBits(Version::getVersionForNumber(1))
+            + $dataBits->getSize();
+        $provisionalVersion = self::chooseVersion($provisionalBitsNeeded, $ecLevel);
+
+        // Use that guess to calculate the right version. I am still not sure
+        // this works in 100% of cases.
+        $bitsNeeded = $headerBits->getSize()
+            + $mode->getCharacterCountBits($provisionalVersion)
+            + $dataBits->getSize();
+        $version = self::chooseVersion($bitsNeeded, $ecLevel);
+
+        if (null !== $forcedVersion) {
+            // Forced version check
+            if ($version->getVersionNumber() <= $forcedVersion->getVersionNumber()) {
+                // Calculated minimum version is same or equal as forced version
+                $version = $forcedVersion;
+            } else {
+                throw new WriterException(
+                    'Invalid version! Calculated version: '
+                    . $version->getVersionNumber()
+                    . ', requested version: '
+                    . $forcedVersion->getVersionNumber()
+                );
+            }
+        }
+
+        $headerAndDataBits = new BitArray();
+        $headerAndDataBits->appendBitArray($headerBits);
+
+        // Find "length" of main segment and write it.
+        $numLetters = (Mode::BYTE() === $mode ? $dataBits->getSizeInBytes() : strlen($content));
+        self::appendLengthInfo($numLetters, $version, $mode, $headerAndDataBits);
+
+        // Put data together into the overall payload.
+        $headerAndDataBits->appendBitArray($dataBits);
+        $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
+        $numDataBytes = $version->getTotalCodewords() - $ecBlocks->getTotalEcCodewords();
+
+        // Terminate the bits properly.
+        self::terminateBits($numDataBytes, $headerAndDataBits);
+
+        // Interleave data bits with error correction code.
+        $finalBits = self::interleaveWithEcBytes(
+            $headerAndDataBits,
+            $version->getTotalCodewords(),
+            $numDataBytes,
+            $ecBlocks->getNumBlocks()
+        );
+
+        // Choose the mask pattern.
+        $dimension = $version->getDimensionForVersion();
+        $matrix = new ByteMatrix($dimension, $dimension);
+        $maskPattern = self::chooseMaskPattern($finalBits, $ecLevel, $version, $matrix);
+
+        // Build the matrix.
+        MatrixUtil::buildMatrix($finalBits, $ecLevel, $version, $maskPattern, $matrix);
+
+        return new QrCode($mode, $ecLevel, $version, $maskPattern, $matrix);
+    }
+
+    /**
+     * Gets the alphanumeric code for a byte.
+     */
+    private static function getAlphanumericCode(int $code) : int
+    {
+        if (isset(self::ALPHANUMERIC_TABLE[$code])) {
+            return self::ALPHANUMERIC_TABLE[$code];
+        }
+
+        return -1;
+    }
+
+    /**
+     * Chooses the best mode for a given content.
+     */
+    private static function chooseMode(string $content, string $encoding = null) : Mode
+    {
+        if (null !== $encoding && 0 === strcasecmp($encoding, 'SHIFT-JIS')) {
+            return self::isOnlyDoubleByteKanji($content) ? Mode::KANJI() : Mode::BYTE();
+        }
+
+        $hasNumeric = false;
+        $hasAlphanumeric = false;
+        $contentLength = strlen($content);
+
+        for ($i = 0; $i < $contentLength; ++$i) {
+            $char = $content[$i];
+
+            if (ctype_digit($char)) {
+                $hasNumeric = true;
+            } elseif (-1 !== self::getAlphanumericCode(ord($char))) {
+                $hasAlphanumeric = true;
+            } else {
+                return Mode::BYTE();
+            }
+        }
+
+        if ($hasAlphanumeric) {
+            return Mode::ALPHANUMERIC();
+        } elseif ($hasNumeric) {
+            return Mode::NUMERIC();
+        }
+
+        return Mode::BYTE();
+    }
+
+    /**
+     * Calculates the mask penalty for a matrix.
+     */
+    private static function calculateMaskPenalty(ByteMatrix $matrix) : int
+    {
+        return (
+            MaskUtil::applyMaskPenaltyRule1($matrix)
+            + MaskUtil::applyMaskPenaltyRule2($matrix)
+            + MaskUtil::applyMaskPenaltyRule3($matrix)
+            + MaskUtil::applyMaskPenaltyRule4($matrix)
+        );
+    }
+
+    /**
+     * Checks if content only consists of double-byte kanji characters.
+     */
+    private static function isOnlyDoubleByteKanji(string $content) : bool
+    {
+        $bytes = @iconv('utf-8', 'SHIFT-JIS', $content);
+
+        if (false === $bytes) {
+            return false;
+        }
+
+        $length = strlen($bytes);
+
+        if (0 !== $length % 2) {
+            return false;
+        }
+
+        for ($i = 0; $i < $length; $i += 2) {
+            $byte = $bytes[$i] & 0xff;
+
+            if (($byte < 0x81 || $byte > 0x9f) && $byte < 0xe0 || $byte > 0xeb) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Chooses the best mask pattern for a matrix.
+     */
+    private static function chooseMaskPattern(
+        BitArray $bits,
+        ErrorCorrectionLevel $ecLevel,
+        Version $version,
+        ByteMatrix $matrix
+    ) : int {
+        $minPenalty = PHP_INT_MAX;
+        $bestMaskPattern = -1;
+
+        for ($maskPattern = 0; $maskPattern < QrCode::NUM_MASK_PATTERNS; ++$maskPattern) {
+            MatrixUtil::buildMatrix($bits, $ecLevel, $version, $maskPattern, $matrix);
+            $penalty = self::calculateMaskPenalty($matrix);
+
+            if ($penalty < $minPenalty) {
+                $minPenalty = $penalty;
+                $bestMaskPattern = $maskPattern;
+            }
+        }
+
+        return $bestMaskPattern;
+    }
+
+    /**
+     * Chooses the best version for the input.
+     *
+     * @throws WriterException if data is too big
+     */
+    private static function chooseVersion(int $numInputBits, ErrorCorrectionLevel $ecLevel) : Version
+    {
+        for ($versionNum = 1; $versionNum <= 40; ++$versionNum) {
+            $version = Version::getVersionForNumber($versionNum);
+            $numBytes = $version->getTotalCodewords();
+
+            $ecBlocks = $version->getEcBlocksForLevel($ecLevel);
+            $numEcBytes = $ecBlocks->getTotalEcCodewords();
+
+            $numDataBytes = $numBytes - $numEcBytes;
+            $totalInputBytes = intdiv($numInputBits + 8, 8);
+
+            if ($numDataBytes >= $totalInputBytes) {
+                return $version;
+            }
+        }
+
+        throw new WriterException('Data too big');
+    }
+
+    /**
+     * Terminates the bits in a bit array.
+     *
+     * @throws WriterException if data bits cannot fit in the QR code
+     * @throws WriterException if bits size does not equal the capacity
+     */
+    private static function terminateBits(int $numDataBytes, BitArray $bits) : void
+    {
+        $capacity = $numDataBytes << 3;
+
+        if ($bits->getSize() > $capacity) {
+            throw new WriterException('Data bits cannot fit in the QR code');
+        }
+
+        for ($i = 0; $i < 4 && $bits->getSize() < $capacity; ++$i) {
+            $bits->appendBit(false);
+        }
+
+        $numBitsInLastByte = $bits->getSize() & 0x7;
+
+        if ($numBitsInLastByte > 0) {
+            for ($i = $numBitsInLastByte; $i < 8; ++$i) {
+                $bits->appendBit(false);
+            }
+        }
+
+        $numPaddingBytes = $numDataBytes - $bits->getSizeInBytes();
+
+        for ($i = 0; $i < $numPaddingBytes; ++$i) {
+            $bits->appendBits(0 === ($i & 0x1) ? 0xec : 0x11, 8);
+        }
+
+        if ($bits->getSize() !== $capacity) {
+            throw new WriterException('Bits size does not equal capacity');
+        }
+    }
+
+    /**
+     * Gets number of data- and EC bytes for a block ID.
+     *
+     * @return int[]
+     * @throws WriterException if block ID is too large
+     * @throws WriterException if EC bytes mismatch
+     * @throws WriterException if RS blocks mismatch
+     * @throws WriterException if total bytes mismatch
+     */
+    private static function getNumDataBytesAndNumEcBytesForBlockId(
+        int $numTotalBytes,
+        int $numDataBytes,
+        int $numRsBlocks,
+        int $blockId
+    ) : array {
+        if ($blockId >= $numRsBlocks) {
+            throw new WriterException('Block ID too large');
+        }
+
+        $numRsBlocksInGroup2 = $numTotalBytes % $numRsBlocks;
+        $numRsBlocksInGroup1 = $numRsBlocks - $numRsBlocksInGroup2;
+        $numTotalBytesInGroup1 = intdiv($numTotalBytes, $numRsBlocks);
+        $numTotalBytesInGroup2 = $numTotalBytesInGroup1 + 1;
+        $numDataBytesInGroup1 = intdiv($numDataBytes, $numRsBlocks);
+        $numDataBytesInGroup2 = $numDataBytesInGroup1 + 1;
+        $numEcBytesInGroup1 = $numTotalBytesInGroup1 - $numDataBytesInGroup1;
+        $numEcBytesInGroup2 = $numTotalBytesInGroup2 - $numDataBytesInGroup2;
+
+        if ($numEcBytesInGroup1 !== $numEcBytesInGroup2) {
+            throw new WriterException('EC bytes mismatch');
+        }
+
+        if ($numRsBlocks !== $numRsBlocksInGroup1 + $numRsBlocksInGroup2) {
+            throw new WriterException('RS blocks mismatch');
+        }
+
+        if ($numTotalBytes !==
+            (($numDataBytesInGroup1 + $numEcBytesInGroup1) * $numRsBlocksInGroup1)
+            + (($numDataBytesInGroup2 + $numEcBytesInGroup2) * $numRsBlocksInGroup2)
+        ) {
+            throw new WriterException('Total bytes mismatch');
+        }
+
+        if ($blockId < $numRsBlocksInGroup1) {
+            return [$numDataBytesInGroup1, $numEcBytesInGroup1];
+        } else {
+            return [$numDataBytesInGroup2, $numEcBytesInGroup2];
+        }
+    }
+
+    /**
+     * Interleaves data with EC bytes.
+     *
+     * @throws WriterException if number of bits and data bytes does not match
+     * @throws WriterException if data bytes does not match offset
+     * @throws WriterException if an interleaving error occurs
+     */
+    private static function interleaveWithEcBytes(
+        BitArray $bits,
+        int $numTotalBytes,
+        int $numDataBytes,
+        int $numRsBlocks
+    ) : BitArray {
+        if ($bits->getSizeInBytes() !== $numDataBytes) {
+            throw new WriterException('Number of bits and data bytes does not match');
+        }
+
+        $dataBytesOffset = 0;
+        $maxNumDataBytes = 0;
+        $maxNumEcBytes   = 0;
+
+        $blocks = new SplFixedArray($numRsBlocks);
+
+        for ($i = 0; $i < $numRsBlocks; ++$i) {
+            list($numDataBytesInBlock, $numEcBytesInBlock) = self::getNumDataBytesAndNumEcBytesForBlockId(
+                $numTotalBytes,
+                $numDataBytes,
+                $numRsBlocks,
+                $i
+            );
+
+            $size = $numDataBytesInBlock;
+            $dataBytes = $bits->toBytes(8 * $dataBytesOffset, $size);
+            $ecBytes = self::generateEcBytes($dataBytes, $numEcBytesInBlock);
+            $blocks[$i] = new BlockPair($dataBytes, $ecBytes);
+
+            $maxNumDataBytes = max($maxNumDataBytes, $size);
+            $maxNumEcBytes = max($maxNumEcBytes, count($ecBytes));
+            $dataBytesOffset += $numDataBytesInBlock;
+        }
+
+        if ($numDataBytes !== $dataBytesOffset) {
+            throw new WriterException('Data bytes does not match offset');
+        }
+
+        $result = new BitArray();
+
+        for ($i = 0; $i < $maxNumDataBytes; ++$i) {
+            foreach ($blocks as $block) {
+                $dataBytes = $block->getDataBytes();
+
+                if ($i < count($dataBytes)) {
+                    $result->appendBits($dataBytes[$i], 8);
+                }
+            }
+        }
+
+        for ($i = 0; $i < $maxNumEcBytes; ++$i) {
+            foreach ($blocks as $block) {
+                $ecBytes = $block->getErrorCorrectionBytes();
+
+                if ($i < count($ecBytes)) {
+                    $result->appendBits($ecBytes[$i], 8);
+                }
+            }
+        }
+
+        if ($numTotalBytes !== $result->getSizeInBytes()) {
+            throw new WriterException(
+                'Interleaving error: ' . $numTotalBytes . ' and ' . $result->getSizeInBytes() . ' differ'
+            );
+        }
+
+        return $result;
+    }
+
+    /**
+     * Generates EC bytes for given data.
+     *
+     * @param  SplFixedArray<int> $dataBytes
+     * @return SplFixedArray<int>
+     */
+    private static function generateEcBytes(SplFixedArray $dataBytes, int $numEcBytesInBlock) : SplFixedArray
+    {
+        $numDataBytes = count($dataBytes);
+        $toEncode = new SplFixedArray($numDataBytes + $numEcBytesInBlock);
+
+        for ($i = 0; $i < $numDataBytes; $i++) {
+            $toEncode[$i] = $dataBytes[$i] & 0xff;
+        }
+
+        $ecBytes = new SplFixedArray($numEcBytesInBlock);
+        $codec = self::getCodec($numDataBytes, $numEcBytesInBlock);
+        $codec->encode($toEncode, $ecBytes);
+
+        return $ecBytes;
+    }
+
+    /**
+     * Gets an RS codec and caches it.
+     */
+    private static function getCodec(int $numDataBytes, int $numEcBytesInBlock) : ReedSolomonCodec
+    {
+        $cacheId = $numDataBytes . '-' . $numEcBytesInBlock;
+
+        if (isset(self::$codecs[$cacheId])) {
+            return self::$codecs[$cacheId];
+        }
+
+        return self::$codecs[$cacheId] = new ReedSolomonCodec(
+            8,
+            0x11d,
+            0,
+            1,
+            $numEcBytesInBlock,
+            255 - $numDataBytes - $numEcBytesInBlock
+        );
+    }
+
+    /**
+     * Appends mode information to a bit array.
+     */
+    private static function appendModeInfo(Mode $mode, BitArray $bits) : void
+    {
+        $bits->appendBits($mode->getBits(), 4);
+    }
+
+    /**
+     * Appends length information to a bit array.
+     *
+     * @throws WriterException if num letters is bigger than expected
+     */
+    private static function appendLengthInfo(int $numLetters, Version $version, Mode $mode, BitArray $bits) : void
+    {
+        $numBits = $mode->getCharacterCountBits($version);
+
+        if ($numLetters >= (1 << $numBits)) {
+            throw new WriterException($numLetters . ' is bigger than ' . ((1 << $numBits) - 1));
+        }
+
+        $bits->appendBits($numLetters, $numBits);
+    }
+
+    /**
+     * Appends bytes to a bit array in a specific mode.
+     *
+     * @throws WriterException if an invalid mode was supplied
+     */
+    private static function appendBytes(string $content, Mode $mode, BitArray $bits, string $encoding) : void
+    {
+        switch ($mode) {
+            case Mode::NUMERIC():
+                self::appendNumericBytes($content, $bits);
+                break;
+
+            case Mode::ALPHANUMERIC():
+                self::appendAlphanumericBytes($content, $bits);
+                break;
+
+            case Mode::BYTE():
+                self::append8BitBytes($content, $bits, $encoding);
+                break;
+
+            case Mode::KANJI():
+                self::appendKanjiBytes($content, $bits);
+                break;
+
+            default:
+                throw new WriterException('Invalid mode: ' . $mode);
+        }
+    }
+
+    /**
+     * Appends numeric bytes to a bit array.
+     */
+    private static function appendNumericBytes(string $content, BitArray $bits) : void
+    {
+        $length = strlen($content);
+        $i = 0;
+
+        while ($i < $length) {
+            $num1 = (int) $content[$i];
+
+            if ($i + 2 < $length) {
+                // Encode three numeric letters in ten bits.
+                $num2 = (int) $content[$i + 1];
+                $num3 = (int) $content[$i + 2];
+                $bits->appendBits($num1 * 100 + $num2 * 10 + $num3, 10);
+                $i += 3;
+            } elseif ($i + 1 < $length) {
+                // Encode two numeric letters in seven bits.
+                $num2 = (int) $content[$i + 1];
+                $bits->appendBits($num1 * 10 + $num2, 7);
+                $i += 2;
+            } else {
+                // Encode one numeric letter in four bits.
+                $bits->appendBits($num1, 4);
+                ++$i;
+            }
+        }
+    }
+
+    /**
+     * Appends alpha-numeric bytes to a bit array.
+     *
+     * @throws WriterException if an invalid alphanumeric code was found
+     */
+    private static function appendAlphanumericBytes(string $content, BitArray $bits) : void
+    {
+        $length = strlen($content);
+        $i = 0;
+
+        while ($i < $length) {
+            $code1 = self::getAlphanumericCode(ord($content[$i]));
+
+            if (-1 === $code1) {
+                throw new WriterException('Invalid alphanumeric code');
+            }
+
+            if ($i + 1 < $length) {
+                $code2 = self::getAlphanumericCode(ord($content[$i + 1]));
+
+                if (-1 === $code2) {
+                    throw new WriterException('Invalid alphanumeric code');
+                }
+
+                // Encode two alphanumeric letters in 11 bits.
+                $bits->appendBits($code1 * 45 + $code2, 11);
+                $i += 2;
+            } else {
+                // Encode one alphanumeric letter in six bits.
+                $bits->appendBits($code1, 6);
+                ++$i;
+            }
+        }
+    }
+
+    /**
+     * Appends regular 8-bit bytes to a bit array.
+     *
+     * @throws WriterException if content cannot be encoded to target encoding
+     */
+    private static function append8BitBytes(string $content, BitArray $bits, string $encoding) : void
+    {
+        $bytes = @iconv('utf-8', $encoding, $content);
+
+        if (false === $bytes) {
+            throw new WriterException('Could not encode content to ' . $encoding);
+        }
+
+        $length = strlen($bytes);
+
+        for ($i = 0; $i < $length; $i++) {
+            $bits->appendBits(ord($bytes[$i]), 8);
+        }
+    }
+
+    /**
+     * Appends KANJI bytes to a bit array.
+     *
+     * @throws WriterException if content does not seem to be encoded in SHIFT-JIS
+     * @throws WriterException if an invalid byte sequence occurs
+     */
+    private static function appendKanjiBytes(string $content, BitArray $bits) : void
+    {
+        if (strlen($content) % 2 > 0) {
+            // We just do a simple length check here. The for loop will check
+            // individual characters.
+            throw new WriterException('Content does not seem to be encoded in SHIFT-JIS');
+        }
+
+        $length = strlen($content);
+
+        for ($i = 0; $i < $length; $i += 2) {
+            $byte1 = ord($content[$i]) & 0xff;
+            $byte2 = ord($content[$i + 1]) & 0xff;
+            $code = ($byte1 << 8) | $byte2;
+
+            if ($code >= 0x8140 && $code <= 0x9ffc) {
+                $subtracted = $code - 0x8140;
+            } elseif ($code >= 0xe040 && $code <= 0xebbf) {
+                $subtracted = $code - 0xc140;
+            } else {
+                throw new WriterException('Invalid byte sequence');
+            }
+
+            $encoded = (($subtracted >> 8) * 0xc0) + ($subtracted & 0xff);
+
+            $bits->appendBits($encoded, 13);
+        }
+    }
+
+    /**
+     * Appends ECI information to a bit array.
+     */
+    private static function appendEci(CharacterSetEci $eci, BitArray $bits) : void
+    {
+        $mode = Mode::ECI();
+        $bits->appendBits($mode->getBits(), 4);
+        $bits->appendBits($eci->getValue(), 8);
+    }
+}

+ 271 - 0
htdocs/includes/bacon/bacon-qr-code/src/Encoder/MaskUtil.php

@@ -0,0 +1,271 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Encoder;
+
+use BaconQrCode\Common\BitUtils;
+use BaconQrCode\Exception\InvalidArgumentException;
+
+/**
+ * Mask utility.
+ */
+final class MaskUtil
+{
+    /**#@+
+     * Penalty weights from section 6.8.2.1
+     */
+    const N1 = 3;
+    const N2 = 3;
+    const N3 = 40;
+    const N4 = 10;
+    /**#@-*/
+
+    private function __construct()
+    {
+    }
+
+    /**
+     * Applies mask penalty rule 1 and returns the penalty.
+     *
+     * Finds repetitive cells with the same color and gives penalty to them.
+     * Example: 00000 or 11111.
+     */
+    public static function applyMaskPenaltyRule1(ByteMatrix $matrix) : int
+    {
+        return (
+            self::applyMaskPenaltyRule1Internal($matrix, true)
+            + self::applyMaskPenaltyRule1Internal($matrix, false)
+        );
+    }
+
+    /**
+     * Applies mask penalty rule 2 and returns the penalty.
+     *
+     * Finds 2x2 blocks with the same color and gives penalty to them. This is
+     * actually equivalent to the spec's rule, which is to find MxN blocks and
+     * give a penalty proportional to (M-1)x(N-1), because this is the number of
+     * 2x2 blocks inside such a block.
+     */
+    public static function applyMaskPenaltyRule2(ByteMatrix $matrix) : int
+    {
+        $penalty = 0;
+        $array = $matrix->getArray();
+        $width = $matrix->getWidth();
+        $height = $matrix->getHeight();
+
+        for ($y = 0; $y < $height - 1; ++$y) {
+            for ($x = 0; $x < $width - 1; ++$x) {
+                $value = $array[$y][$x];
+
+                if ($value === $array[$y][$x + 1]
+                    && $value === $array[$y + 1][$x]
+                    && $value === $array[$y + 1][$x + 1]
+                ) {
+                    ++$penalty;
+                }
+            }
+        }
+
+        return self::N2 * $penalty;
+    }
+
+    /**
+     * Applies mask penalty rule 3 and returns the penalty.
+     *
+     * Finds consecutive cells of 00001011101 or 10111010000, and gives penalty
+     * to them. If we find patterns like 000010111010000, we give penalties
+     * twice (i.e. 40 * 2).
+     */
+    public static function applyMaskPenaltyRule3(ByteMatrix $matrix) : int
+    {
+        $penalty = 0;
+        $array = $matrix->getArray();
+        $width = $matrix->getWidth();
+        $height = $matrix->getHeight();
+
+        for ($y = 0; $y < $height; ++$y) {
+            for ($x = 0; $x < $width; ++$x) {
+                if ($x + 6 < $width
+                    && 1 === $array[$y][$x]
+                    && 0 === $array[$y][$x + 1]
+                    && 1 === $array[$y][$x + 2]
+                    && 1 === $array[$y][$x + 3]
+                    && 1 === $array[$y][$x + 4]
+                    && 0 === $array[$y][$x + 5]
+                    && 1 === $array[$y][$x + 6]
+                    && (
+                        (
+                            $x + 10 < $width
+                            && 0 === $array[$y][$x + 7]
+                            && 0 === $array[$y][$x + 8]
+                            && 0 === $array[$y][$x + 9]
+                            && 0 === $array[$y][$x + 10]
+                        )
+                        || (
+                            $x - 4 >= 0
+                            && 0 === $array[$y][$x - 1]
+                            && 0 === $array[$y][$x - 2]
+                            && 0 === $array[$y][$x - 3]
+                            && 0 === $array[$y][$x - 4]
+                        )
+                    )
+                ) {
+                    $penalty += self::N3;
+                }
+
+                if ($y + 6 < $height
+                    && 1 === $array[$y][$x]
+                    && 0 === $array[$y + 1][$x]
+                    && 1 === $array[$y + 2][$x]
+                    && 1 === $array[$y + 3][$x]
+                    && 1 === $array[$y + 4][$x]
+                    && 0 === $array[$y + 5][$x]
+                    && 1 === $array[$y + 6][$x]
+                    && (
+                        (
+                            $y + 10 < $height
+                            && 0 === $array[$y + 7][$x]
+                            && 0 === $array[$y + 8][$x]
+                            && 0 === $array[$y + 9][$x]
+                            && 0 === $array[$y + 10][$x]
+                        )
+                        || (
+                            $y - 4 >= 0
+                            && 0 === $array[$y - 1][$x]
+                            && 0 === $array[$y - 2][$x]
+                            && 0 === $array[$y - 3][$x]
+                            && 0 === $array[$y - 4][$x]
+                        )
+                    )
+                ) {
+                    $penalty += self::N3;
+                }
+            }
+        }
+
+        return $penalty;
+    }
+
+    /**
+     * Applies mask penalty rule 4 and returns the penalty.
+     *
+     * Calculates the ratio of dark cells and gives penalty if the ratio is far
+     * from 50%. It gives 10 penalty for 5% distance.
+     */
+    public static function applyMaskPenaltyRule4(ByteMatrix $matrix) : int
+    {
+        $numDarkCells = 0;
+
+        $array = $matrix->getArray();
+        $width = $matrix->getWidth();
+        $height = $matrix->getHeight();
+
+        for ($y = 0; $y < $height; ++$y) {
+            $arrayY = $array[$y];
+
+            for ($x = 0; $x < $width; ++$x) {
+                if (1 === $arrayY[$x]) {
+                    ++$numDarkCells;
+                }
+            }
+        }
+
+        $numTotalCells = $height * $width;
+        $darkRatio = $numDarkCells / $numTotalCells;
+        $fixedPercentVariances = (int) (abs($darkRatio - 0.5) * 20);
+
+        return $fixedPercentVariances * self::N4;
+    }
+
+    /**
+     * Returns the mask bit for "getMaskPattern" at "x" and "y".
+     *
+     * See 8.8 of JISX0510:2004 for mask pattern conditions.
+     *
+     * @throws InvalidArgumentException if an invalid mask pattern was supplied
+     */
+    public static function getDataMaskBit(int $maskPattern, int $x, int $y) : bool
+    {
+        switch ($maskPattern) {
+            case 0:
+                $intermediate = ($y + $x) & 0x1;
+                break;
+
+            case 1:
+                $intermediate = $y & 0x1;
+                break;
+
+            case 2:
+                $intermediate = $x % 3;
+                break;
+
+            case 3:
+                $intermediate = ($y + $x) % 3;
+                break;
+
+            case 4:
+                $intermediate = (BitUtils::unsignedRightShift($y, 1) + (int) ($x / 3)) & 0x1;
+                break;
+
+            case 5:
+                $temp = $y * $x;
+                $intermediate = ($temp & 0x1) + ($temp % 3);
+                break;
+
+            case 6:
+                $temp = $y * $x;
+                $intermediate = (($temp & 0x1) + ($temp % 3)) & 0x1;
+                break;
+
+            case 7:
+                $temp = $y * $x;
+                $intermediate = (($temp % 3) + (($y + $x) & 0x1)) & 0x1;
+                break;
+
+            default:
+                throw new InvalidArgumentException('Invalid mask pattern: ' . $maskPattern);
+        }
+
+        return 0 == $intermediate;
+    }
+
+    /**
+     * Helper function for applyMaskPenaltyRule1.
+     *
+     * We need this for doing this calculation in both vertical and horizontal
+     * orders respectively.
+     */
+    private static function applyMaskPenaltyRule1Internal(ByteMatrix $matrix, bool $isHorizontal) : int
+    {
+        $penalty = 0;
+        $iLimit = $isHorizontal ? $matrix->getHeight() : $matrix->getWidth();
+        $jLimit = $isHorizontal ? $matrix->getWidth() : $matrix->getHeight();
+        $array = $matrix->getArray();
+
+        for ($i = 0; $i < $iLimit; ++$i) {
+            $numSameBitCells = 0;
+            $prevBit = -1;
+
+            for ($j = 0; $j < $jLimit; $j++) {
+                $bit = $isHorizontal ? $array[$i][$j] : $array[$j][$i];
+
+                if ($bit === $prevBit) {
+                    ++$numSameBitCells;
+                } else {
+                    if ($numSameBitCells >= 5) {
+                        $penalty += self::N1 + ($numSameBitCells - 5);
+                    }
+
+                    $numSameBitCells = 1;
+                    $prevBit = $bit;
+                }
+            }
+
+            if ($numSameBitCells >= 5) {
+                $penalty += self::N1 + ($numSameBitCells - 5);
+            }
+        }
+
+        return $penalty;
+    }
+}

+ 513 - 0
htdocs/includes/bacon/bacon-qr-code/src/Encoder/MatrixUtil.php

@@ -0,0 +1,513 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Encoder;
+
+use BaconQrCode\Common\BitArray;
+use BaconQrCode\Common\ErrorCorrectionLevel;
+use BaconQrCode\Common\Version;
+use BaconQrCode\Exception\RuntimeException;
+use BaconQrCode\Exception\WriterException;
+
+/**
+ * Matrix utility.
+ */
+final class MatrixUtil
+{
+    /**
+     * Position detection pattern.
+     */
+    private const POSITION_DETECTION_PATTERN = [
+        [1, 1, 1, 1, 1, 1, 1],
+        [1, 0, 0, 0, 0, 0, 1],
+        [1, 0, 1, 1, 1, 0, 1],
+        [1, 0, 1, 1, 1, 0, 1],
+        [1, 0, 1, 1, 1, 0, 1],
+        [1, 0, 0, 0, 0, 0, 1],
+        [1, 1, 1, 1, 1, 1, 1],
+    ];
+
+    /**
+     * Position adjustment pattern.
+     */
+    private const POSITION_ADJUSTMENT_PATTERN = [
+        [1, 1, 1, 1, 1],
+        [1, 0, 0, 0, 1],
+        [1, 0, 1, 0, 1],
+        [1, 0, 0, 0, 1],
+        [1, 1, 1, 1, 1],
+    ];
+
+    /**
+     * Coordinates for position adjustment patterns for each version.
+     */
+    private const POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = [
+        [null, null, null, null, null, null, null], // Version 1
+        [   6,   18, null, null, null, null, null], // Version 2
+        [   6,   22, null, null, null, null, null], // Version 3
+        [   6,   26, null, null, null, null, null], // Version 4
+        [   6,   30, null, null, null, null, null], // Version 5
+        [   6,   34, null, null, null, null, null], // Version 6
+        [   6,   22,   38, null, null, null, null], // Version 7
+        [   6,   24,   42, null, null, null, null], // Version 8
+        [   6,   26,   46, null, null, null, null], // Version 9
+        [   6,   28,   50, null, null, null, null], // Version 10
+        [   6,   30,   54, null, null, null, null], // Version 11
+        [   6,   32,   58, null, null, null, null], // Version 12
+        [   6,   34,   62, null, null, null, null], // Version 13
+        [   6,   26,   46,   66, null, null, null], // Version 14
+        [   6,   26,   48,   70, null, null, null], // Version 15
+        [   6,   26,   50,   74, null, null, null], // Version 16
+        [   6,   30,   54,   78, null, null, null], // Version 17
+        [   6,   30,   56,   82, null, null, null], // Version 18
+        [   6,   30,   58,   86, null, null, null], // Version 19
+        [   6,   34,   62,   90, null, null, null], // Version 20
+        [   6,   28,   50,   72,   94, null, null], // Version 21
+        [   6,   26,   50,   74,   98, null, null], // Version 22
+        [   6,   30,   54,   78,  102, null, null], // Version 23
+        [   6,   28,   54,   80,  106, null, null], // Version 24
+        [   6,   32,   58,   84,  110, null, null], // Version 25
+        [   6,   30,   58,   86,  114, null, null], // Version 26
+        [   6,   34,   62,   90,  118, null, null], // Version 27
+        [   6,   26,   50,   74,   98,  122, null], // Version 28
+        [   6,   30,   54,   78,  102,  126, null], // Version 29
+        [   6,   26,   52,   78,  104,  130, null], // Version 30
+        [   6,   30,   56,   82,  108,  134, null], // Version 31
+        [   6,   34,   60,   86,  112,  138, null], // Version 32
+        [   6,   30,   58,   86,  114,  142, null], // Version 33
+        [   6,   34,   62,   90,  118,  146, null], // Version 34
+        [   6,   30,   54,   78,  102,  126,  150], // Version 35
+        [   6,   24,   50,   76,  102,  128,  154], // Version 36
+        [   6,   28,   54,   80,  106,  132,  158], // Version 37
+        [   6,   32,   58,   84,  110,  136,  162], // Version 38
+        [   6,   26,   54,   82,  110,  138,  166], // Version 39
+        [   6,   30,   58,   86,  114,  142,  170], // Version 40
+    ];
+
+    /**
+     * Type information coordinates.
+     */
+    private const TYPE_INFO_COORDINATES = [
+        [8, 0],
+        [8, 1],
+        [8, 2],
+        [8, 3],
+        [8, 4],
+        [8, 5],
+        [8, 7],
+        [8, 8],
+        [7, 8],
+        [5, 8],
+        [4, 8],
+        [3, 8],
+        [2, 8],
+        [1, 8],
+        [0, 8],
+    ];
+
+    /**
+     * Version information polynomial.
+     */
+    private const VERSION_INFO_POLY = 0x1f25;
+
+    /**
+     * Type information polynomial.
+     */
+    private const TYPE_INFO_POLY = 0x537;
+
+    /**
+     * Type information mask pattern.
+     */
+    private const TYPE_INFO_MASK_PATTERN = 0x5412;
+
+    /**
+     * Clears a given matrix.
+     */
+    public static function clearMatrix(ByteMatrix $matrix) : void
+    {
+        $matrix->clear(-1);
+    }
+
+    /**
+     * Builds a complete matrix.
+     */
+    public static function buildMatrix(
+        BitArray $dataBits,
+        ErrorCorrectionLevel $level,
+        Version $version,
+        int $maskPattern,
+        ByteMatrix $matrix
+    ) : void {
+        self::clearMatrix($matrix);
+        self::embedBasicPatterns($version, $matrix);
+        self::embedTypeInfo($level, $maskPattern, $matrix);
+        self::maybeEmbedVersionInfo($version, $matrix);
+        self::embedDataBits($dataBits, $maskPattern, $matrix);
+    }
+
+    /**
+     * Removes the position detection patterns from a matrix.
+     *
+     * This can be useful if you need to render those patterns separately.
+     */
+    public static function removePositionDetectionPatterns(ByteMatrix $matrix) : void
+    {
+        $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
+
+        self::removePositionDetectionPattern(0, 0, $matrix);
+        self::removePositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
+        self::removePositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
+    }
+
+    /**
+     * Embeds type information into a matrix.
+     */
+    private static function embedTypeInfo(ErrorCorrectionLevel $level, int $maskPattern, ByteMatrix $matrix) : void
+    {
+        $typeInfoBits = new BitArray();
+        self::makeTypeInfoBits($level, $maskPattern, $typeInfoBits);
+
+        $typeInfoBitsSize = $typeInfoBits->getSize();
+
+        for ($i = 0; $i < $typeInfoBitsSize; ++$i) {
+            $bit = $typeInfoBits->get($typeInfoBitsSize - 1 - $i);
+
+            $x1 = self::TYPE_INFO_COORDINATES[$i][0];
+            $y1 = self::TYPE_INFO_COORDINATES[$i][1];
+
+            $matrix->set($x1, $y1, (int) $bit);
+
+            if ($i < 8) {
+                $x2 = $matrix->getWidth() - $i - 1;
+                $y2 = 8;
+            } else {
+                $x2 = 8;
+                $y2 = $matrix->getHeight() - 7 + ($i - 8);
+            }
+
+            $matrix->set($x2, $y2, (int) $bit);
+        }
+    }
+
+    /**
+     * Generates type information bits and appends them to a bit array.
+     *
+     * @throws RuntimeException if bit array resulted in invalid size
+     */
+    private static function makeTypeInfoBits(ErrorCorrectionLevel $level, int $maskPattern, BitArray $bits) : void
+    {
+        $typeInfo = ($level->getBits() << 3) | $maskPattern;
+        $bits->appendBits($typeInfo, 5);
+
+        $bchCode = self::calculateBchCode($typeInfo, self::TYPE_INFO_POLY);
+        $bits->appendBits($bchCode, 10);
+
+        $maskBits = new BitArray();
+        $maskBits->appendBits(self::TYPE_INFO_MASK_PATTERN, 15);
+        $bits->xorBits($maskBits);
+
+        if (15 !== $bits->getSize()) {
+            throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
+        }
+    }
+
+    /**
+     * Embeds version information if required.
+     */
+    private static function maybeEmbedVersionInfo(Version $version, ByteMatrix $matrix) : void
+    {
+        if ($version->getVersionNumber() < 7) {
+            return;
+        }
+
+        $versionInfoBits = new BitArray();
+        self::makeVersionInfoBits($version, $versionInfoBits);
+
+        $bitIndex = 6 * 3 - 1;
+
+        for ($i = 0; $i < 6; ++$i) {
+            for ($j = 0; $j < 3; ++$j) {
+                $bit = $versionInfoBits->get($bitIndex);
+                --$bitIndex;
+
+                $matrix->set($i, $matrix->getHeight() - 11 + $j, (int) $bit);
+                $matrix->set($matrix->getHeight() - 11 + $j, $i, (int) $bit);
+            }
+        }
+    }
+
+    /**
+     * Generates version information bits and appends them to a bit array.
+     *
+     * @throws RuntimeException if bit array resulted in invalid size
+     */
+    private static function makeVersionInfoBits(Version $version, BitArray $bits) : void
+    {
+        $bits->appendBits($version->getVersionNumber(), 6);
+
+        $bchCode = self::calculateBchCode($version->getVersionNumber(), self::VERSION_INFO_POLY);
+        $bits->appendBits($bchCode, 12);
+
+        if (18 !== $bits->getSize()) {
+            throw new RuntimeException('Bit array resulted in invalid size: ' . $bits->getSize());
+        }
+    }
+
+    /**
+     * Calculates the BCH code for a value and a polynomial.
+     */
+    private static function calculateBchCode(int $value, int $poly) : int
+    {
+        $msbSetInPoly = self::findMsbSet($poly);
+        $value <<= $msbSetInPoly - 1;
+
+        while (self::findMsbSet($value) >= $msbSetInPoly) {
+            $value ^= $poly << (self::findMsbSet($value) - $msbSetInPoly);
+        }
+
+        return $value;
+    }
+
+    /**
+     * Finds and MSB set.
+     */
+    private static function findMsbSet(int $value) : int
+    {
+        $numDigits = 0;
+
+        while (0 !== $value) {
+            $value >>= 1;
+            ++$numDigits;
+        }
+
+        return $numDigits;
+    }
+
+    /**
+     * Embeds basic patterns into a matrix.
+     */
+    private static function embedBasicPatterns(Version $version, ByteMatrix $matrix) : void
+    {
+        self::embedPositionDetectionPatternsAndSeparators($matrix);
+        self::embedDarkDotAtLeftBottomCorner($matrix);
+        self::maybeEmbedPositionAdjustmentPatterns($version, $matrix);
+        self::embedTimingPatterns($matrix);
+    }
+
+    /**
+     * Embeds position detection patterns and separators into a byte matrix.
+     */
+    private static function embedPositionDetectionPatternsAndSeparators(ByteMatrix $matrix) : void
+    {
+        $pdpWidth = count(self::POSITION_DETECTION_PATTERN[0]);
+
+        self::embedPositionDetectionPattern(0, 0, $matrix);
+        self::embedPositionDetectionPattern($matrix->getWidth() - $pdpWidth, 0, $matrix);
+        self::embedPositionDetectionPattern(0, $matrix->getWidth() - $pdpWidth, $matrix);
+
+        $hspWidth = 8;
+
+        self::embedHorizontalSeparationPattern(0, $hspWidth - 1, $matrix);
+        self::embedHorizontalSeparationPattern($matrix->getWidth() - $hspWidth, $hspWidth - 1, $matrix);
+        self::embedHorizontalSeparationPattern(0, $matrix->getWidth() - $hspWidth, $matrix);
+
+        $vspSize = 7;
+
+        self::embedVerticalSeparationPattern($vspSize, 0, $matrix);
+        self::embedVerticalSeparationPattern($matrix->getHeight() - $vspSize - 1, 0, $matrix);
+        self::embedVerticalSeparationPattern($vspSize, $matrix->getHeight() - $vspSize, $matrix);
+    }
+
+    /**
+     * Embeds a single position detection pattern into a byte matrix.
+     */
+    private static function embedPositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
+    {
+        for ($y = 0; $y < 7; ++$y) {
+            for ($x = 0; $x < 7; ++$x) {
+                $matrix->set($xStart + $x, $yStart + $y, self::POSITION_DETECTION_PATTERN[$y][$x]);
+            }
+        }
+    }
+
+    private static function removePositionDetectionPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
+    {
+        for ($y = 0; $y < 7; ++$y) {
+            for ($x = 0; $x < 7; ++$x) {
+                $matrix->set($xStart + $x, $yStart + $y, 0);
+            }
+        }
+    }
+
+    /**
+     * Embeds a single horizontal separation pattern.
+     *
+     * @throws RuntimeException if a byte was already set
+     */
+    private static function embedHorizontalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
+    {
+        for ($x = 0; $x < 8; $x++) {
+            if (-1 !== $matrix->get($xStart + $x, $yStart)) {
+                throw new RuntimeException('Byte already set');
+            }
+
+            $matrix->set($xStart + $x, $yStart, 0);
+        }
+    }
+
+    /**
+     * Embeds a single vertical separation pattern.
+     *
+     * @throws RuntimeException if a byte was already set
+     */
+    private static function embedVerticalSeparationPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
+    {
+        for ($y = 0; $y < 7; $y++) {
+            if (-1 !== $matrix->get($xStart, $yStart + $y)) {
+                throw new RuntimeException('Byte already set');
+            }
+
+            $matrix->set($xStart, $yStart + $y, 0);
+        }
+    }
+
+    /**
+     * Embeds a dot at the left bottom corner.
+     *
+     * @throws RuntimeException if a byte was already set to 0
+     */
+    private static function embedDarkDotAtLeftBottomCorner(ByteMatrix $matrix) : void
+    {
+        if (0 === $matrix->get(8, $matrix->getHeight() - 8)) {
+            throw new RuntimeException('Byte already set to 0');
+        }
+
+        $matrix->set(8, $matrix->getHeight() - 8, 1);
+    }
+
+    /**
+     * Embeds position adjustment patterns if required.
+     */
+    private static function maybeEmbedPositionAdjustmentPatterns(Version $version, ByteMatrix $matrix) : void
+    {
+        if ($version->getVersionNumber() < 2) {
+            return;
+        }
+
+        $index = $version->getVersionNumber() - 1;
+
+        $coordinates = self::POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[$index];
+        $numCoordinates = count($coordinates);
+
+        for ($i = 0; $i < $numCoordinates; ++$i) {
+            for ($j = 0; $j < $numCoordinates; ++$j) {
+                $y = $coordinates[$i];
+                $x = $coordinates[$j];
+
+                if (null === $x || null === $y) {
+                    continue;
+                }
+
+                if (-1 === $matrix->get($x, $y)) {
+                    self::embedPositionAdjustmentPattern($x - 2, $y - 2, $matrix);
+                }
+            }
+        }
+    }
+
+    /**
+     * Embeds a single position adjustment pattern.
+     */
+    private static function embedPositionAdjustmentPattern(int $xStart, int $yStart, ByteMatrix $matrix) : void
+    {
+        for ($y = 0; $y < 5; $y++) {
+            for ($x = 0; $x < 5; $x++) {
+                $matrix->set($xStart + $x, $yStart + $y, self::POSITION_ADJUSTMENT_PATTERN[$y][$x]);
+            }
+        }
+    }
+
+    /**
+     * Embeds timing patterns into a matrix.
+     */
+    private static function embedTimingPatterns(ByteMatrix $matrix) : void
+    {
+        $matrixWidth = $matrix->getWidth();
+
+        for ($i = 8; $i < $matrixWidth - 8; ++$i) {
+            $bit = ($i + 1) % 2;
+
+            if (-1 === $matrix->get($i, 6)) {
+                $matrix->set($i, 6, $bit);
+            }
+
+            if (-1 === $matrix->get(6, $i)) {
+                $matrix->set(6, $i, $bit);
+            }
+        }
+    }
+
+    /**
+     * Embeds "dataBits" using "getMaskPattern".
+     *
+     * For debugging purposes, it skips masking process if "getMaskPattern" is -1. See 8.7 of JISX0510:2004 (p.38) for
+     * how to embed data bits.
+     *
+     * @throws WriterException if not all bits could be consumed
+     */
+    private static function embedDataBits(BitArray $dataBits, int $maskPattern, ByteMatrix $matrix) : void
+    {
+        $bitIndex = 0;
+        $direction = -1;
+
+        // Start from the right bottom cell.
+        $x = $matrix->getWidth() - 1;
+        $y = $matrix->getHeight() - 1;
+
+        while ($x > 0) {
+            // Skip vertical timing pattern.
+            if (6 === $x) {
+                --$x;
+            }
+
+            while ($y >= 0 && $y < $matrix->getHeight()) {
+                for ($i = 0; $i < 2; $i++) {
+                    $xx = $x - $i;
+
+                    // Skip the cell if it's not empty.
+                    if (-1 !== $matrix->get($xx, $y)) {
+                        continue;
+                    }
+
+                    if ($bitIndex < $dataBits->getSize()) {
+                        $bit = $dataBits->get($bitIndex);
+                        ++$bitIndex;
+                    } else {
+                        // Padding bit. If there is no bit left, we'll fill the
+                        // left cells with 0, as described in 8.4.9 of
+                        // JISX0510:2004 (p. 24).
+                        $bit = false;
+                    }
+
+                    // Skip masking if maskPattern is -1.
+                    if (-1 !== $maskPattern && MaskUtil::getDataMaskBit($maskPattern, $xx, $y)) {
+                        $bit = ! $bit;
+                    }
+
+                    $matrix->set($xx, $y, (int) $bit);
+                }
+
+                $y += $direction;
+            }
+
+            $direction  = -$direction;
+            $y += $direction;
+            $x -= 2;
+        }
+
+        // All bits should be consumed
+        if ($dataBits->getSize() !== $bitIndex) {
+            throw new WriterException('Not all bits consumed (' . $bitIndex . ' out of ' . $dataBits->getSize() .')');
+        }
+    }
+}

+ 141 - 0
htdocs/includes/bacon/bacon-qr-code/src/Encoder/QrCode.php

@@ -0,0 +1,141 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Encoder;
+
+use BaconQrCode\Common\ErrorCorrectionLevel;
+use BaconQrCode\Common\Mode;
+use BaconQrCode\Common\Version;
+
+/**
+ * QR code.
+ */
+final class QrCode
+{
+    /**
+     * Number of possible mask patterns.
+     */
+    public const NUM_MASK_PATTERNS = 8;
+
+    /**
+     * Mode of the QR code.
+     *
+     * @var Mode
+     */
+    private $mode;
+
+    /**
+     * EC level of the QR code.
+     *
+     * @var ErrorCorrectionLevel
+     */
+    private $errorCorrectionLevel;
+
+    /**
+     * Version of the QR code.
+     *
+     * @var Version
+     */
+    private $version;
+
+    /**
+     * Mask pattern of the QR code.
+     *
+     * @var int
+     */
+    private $maskPattern = -1;
+
+    /**
+     * Matrix of the QR code.
+     *
+     * @var ByteMatrix
+     */
+    private $matrix;
+
+    public function __construct(
+        Mode $mode,
+        ErrorCorrectionLevel $errorCorrectionLevel,
+        Version $version,
+        int $maskPattern,
+        ByteMatrix $matrix
+    ) {
+        $this->mode = $mode;
+        $this->errorCorrectionLevel = $errorCorrectionLevel;
+        $this->version = $version;
+        $this->maskPattern = $maskPattern;
+        $this->matrix = $matrix;
+    }
+
+    /**
+     * Gets the mode.
+     */
+    public function getMode() : Mode
+    {
+        return $this->mode;
+    }
+
+    /**
+     * Gets the EC level.
+     */
+    public function getErrorCorrectionLevel() : ErrorCorrectionLevel
+    {
+        return $this->errorCorrectionLevel;
+    }
+
+    /**
+     * Gets the version.
+     */
+    public function getVersion() : Version
+    {
+        return $this->version;
+    }
+
+    /**
+     * Gets the mask pattern.
+     */
+    public function getMaskPattern() : int
+    {
+        return $this->maskPattern;
+    }
+
+    /**
+     * Gets the matrix.
+     *
+     * @return ByteMatrix
+     */
+    public function getMatrix()
+    {
+        return $this->matrix;
+    }
+
+    /**
+     * Validates whether a mask pattern is valid.
+     */
+    public static function isValidMaskPattern(int $maskPattern) : bool
+    {
+        return $maskPattern > 0 && $maskPattern < self::NUM_MASK_PATTERNS;
+    }
+
+    /**
+     * Returns a string representation of the QR code.
+     */
+    public function __toString() : string
+    {
+        $result = "<<\n"
+                . ' mode: ' . $this->mode . "\n"
+                . ' ecLevel: ' . $this->errorCorrectionLevel . "\n"
+                . ' version: ' . $this->version . "\n"
+                . ' maskPattern: ' . $this->maskPattern . "\n";
+
+        if ($this->matrix === null) {
+            $result .= " matrix: null\n";
+        } else {
+            $result .= " matrix:\n";
+            $result .= $this->matrix;
+        }
+
+        $result .= ">>\n";
+
+        return $result;
+    }
+}

+ 10 - 0
htdocs/includes/bacon/bacon-qr-code/src/Exception/ExceptionInterface.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Exception;
+
+use Throwable;
+
+interface ExceptionInterface extends Throwable
+{
+}

+ 8 - 0
htdocs/includes/bacon/bacon-qr-code/src/Exception/InvalidArgumentException.php

@@ -0,0 +1,8 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Exception;
+
+final class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}

+ 8 - 0
htdocs/includes/bacon/bacon-qr-code/src/Exception/OutOfBoundsException.php

@@ -0,0 +1,8 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Exception;
+
+final class OutOfBoundsException extends \OutOfBoundsException implements ExceptionInterface
+{
+}

+ 8 - 0
htdocs/includes/bacon/bacon-qr-code/src/Exception/RuntimeException.php

@@ -0,0 +1,8 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Exception;
+
+final class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}

+ 8 - 0
htdocs/includes/bacon/bacon-qr-code/src/Exception/UnexpectedValueException.php

@@ -0,0 +1,8 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Exception;
+
+final class UnexpectedValueException extends \UnexpectedValueException implements ExceptionInterface
+{
+}

+ 8 - 0
htdocs/includes/bacon/bacon-qr-code/src/Exception/WriterException.php

@@ -0,0 +1,8 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Exception;
+
+final class WriterException extends \RuntimeException implements ExceptionInterface
+{
+}

+ 57 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Alpha.php

@@ -0,0 +1,57 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Color;
+
+use BaconQrCode\Exception;
+
+final class Alpha implements ColorInterface
+{
+    /**
+     * @var int
+     */
+    private $alpha;
+
+    /**
+     * @var ColorInterface
+     */
+    private $baseColor;
+
+    /**
+     * @param int $alpha the alpha value, 0 to 100
+     */
+    public function __construct(int $alpha, ColorInterface $baseColor)
+    {
+        if ($alpha < 0 || $alpha > 100) {
+            throw new Exception\InvalidArgumentException('Alpha must be between 0 and 100');
+        }
+
+        $this->alpha = $alpha;
+        $this->baseColor = $baseColor;
+    }
+
+    public function getAlpha() : int
+    {
+        return $this->alpha;
+    }
+
+    public function getBaseColor() : ColorInterface
+    {
+        return $this->baseColor;
+    }
+
+    public function toRgb() : Rgb
+    {
+        return $this->baseColor->toRgb();
+    }
+
+    public function toCmyk() : Cmyk
+    {
+        return $this->baseColor->toCmyk();
+    }
+
+    public function toGray() : Gray
+    {
+        return $this->baseColor->toGray();
+    }
+}

+ 103 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Cmyk.php

@@ -0,0 +1,103 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Color;
+
+use BaconQrCode\Exception;
+
+final class Cmyk implements ColorInterface
+{
+    /**
+     * @var int
+     */
+    private $cyan;
+
+    /**
+     * @var int
+     */
+    private $magenta;
+
+    /**
+     * @var int
+     */
+    private $yellow;
+
+    /**
+     * @var int
+     */
+    private $black;
+
+    /**
+     * @param int $cyan the cyan amount, 0 to 100
+     * @param int $magenta the magenta amount, 0 to 100
+     * @param int $yellow the yellow amount, 0 to 100
+     * @param int $black the black amount, 0 to 100
+     */
+    public function __construct(int $cyan, int $magenta, int $yellow, int $black)
+    {
+        if ($cyan < 0 || $cyan > 100) {
+            throw new Exception\InvalidArgumentException('Cyan must be between 0 and 100');
+        }
+
+        if ($magenta < 0 || $magenta > 100) {
+            throw new Exception\InvalidArgumentException('Magenta must be between 0 and 100');
+        }
+
+        if ($yellow < 0 || $yellow > 100) {
+            throw new Exception\InvalidArgumentException('Yellow must be between 0 and 100');
+        }
+
+        if ($black < 0 || $black > 100) {
+            throw new Exception\InvalidArgumentException('Black must be between 0 and 100');
+        }
+
+        $this->cyan = $cyan;
+        $this->magenta = $magenta;
+        $this->yellow = $yellow;
+        $this->black = $black;
+    }
+
+    public function getCyan() : int
+    {
+        return $this->cyan;
+    }
+
+    public function getMagenta() : int
+    {
+        return $this->magenta;
+    }
+
+    public function getYellow() : int
+    {
+        return $this->yellow;
+    }
+
+    public function getBlack() : int
+    {
+        return $this->black;
+    }
+
+    public function toRgb() : Rgb
+    {
+        $k = $this->black / 100;
+        $c = (-$k * $this->cyan + $k * 100 + $this->cyan) / 100;
+        $m = (-$k * $this->magenta + $k * 100 + $this->magenta) / 100;
+        $y = (-$k * $this->yellow + $k * 100 + $this->yellow) / 100;
+
+        return new Rgb(
+            (int) (-$c * 255 + 255),
+            (int) (-$m * 255 + 255),
+            (int) (-$y * 255 + 255)
+        );
+    }
+
+    public function toCmyk() : Cmyk
+    {
+        return $this;
+    }
+
+    public function toGray() : Gray
+    {
+        return $this->toRgb()->toGray();
+    }
+}

+ 22 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/ColorInterface.php

@@ -0,0 +1,22 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Color;
+
+interface ColorInterface
+{
+    /**
+     * Converts the color to RGB.
+     */
+    public function toRgb() : Rgb;
+
+    /**
+     * Converts the color to CMYK.
+     */
+    public function toCmyk() : Cmyk;
+
+    /**
+     * Converts the color to gray.
+     */
+    public function toGray() : Gray;
+}

+ 46 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Gray.php

@@ -0,0 +1,46 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Color;
+
+use BaconQrCode\Exception;
+
+final class Gray implements ColorInterface
+{
+    /**
+     * @var int
+     */
+    private $gray;
+
+    /**
+     * @param int $gray the gray value between 0 (black) and 100 (white)
+     */
+    public function __construct(int $gray)
+    {
+        if ($gray < 0 || $gray > 100) {
+            throw new Exception\InvalidArgumentException('Gray must be between 0 and 100');
+        }
+
+        $this->gray = (int) $gray;
+    }
+
+    public function getGray() : int
+    {
+        return $this->gray;
+    }
+
+    public function toRgb() : Rgb
+    {
+        return new Rgb((int) ($this->gray * 2.55), (int) ($this->gray * 2.55), (int) ($this->gray * 2.55));
+    }
+
+    public function toCmyk() : Cmyk
+    {
+        return new Cmyk(0, 0, 0, 100 - $this->gray);
+    }
+
+    public function toGray() : Gray
+    {
+        return $this;
+    }
+}

+ 88 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Color/Rgb.php

@@ -0,0 +1,88 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Color;
+
+use BaconQrCode\Exception;
+
+final class Rgb implements ColorInterface
+{
+    /**
+     * @var int
+     */
+    private $red;
+
+    /**
+     * @var int
+     */
+    private $green;
+
+    /**
+     * @var int
+     */
+    private $blue;
+
+    /**
+     * @param int $red the red amount of the color, 0 to 255
+     * @param int $green the green amount of the color, 0 to 255
+     * @param int $blue the blue amount of the color, 0 to 255
+     */
+    public function __construct(int $red, int $green, int $blue)
+    {
+        if ($red < 0 || $red > 255) {
+            throw new Exception\InvalidArgumentException('Red must be between 0 and 255');
+        }
+
+        if ($green < 0 || $green > 255) {
+            throw new Exception\InvalidArgumentException('Green must be between 0 and 255');
+        }
+
+        if ($blue < 0 || $blue > 255) {
+            throw new Exception\InvalidArgumentException('Blue must be between 0 and 255');
+        }
+
+        $this->red = $red;
+        $this->green = $green;
+        $this->blue = $blue;
+    }
+
+    public function getRed() : int
+    {
+        return $this->red;
+    }
+
+    public function getGreen() : int
+    {
+        return $this->green;
+    }
+
+    public function getBlue() : int
+    {
+        return $this->blue;
+    }
+
+    public function toRgb() : Rgb
+    {
+        return $this;
+    }
+
+    public function toCmyk() : Cmyk
+    {
+        $c = 1 - ($this->red / 255);
+        $m = 1 - ($this->green / 255);
+        $y = 1 - ($this->blue / 255);
+        $k = min($c, $m, $y);
+
+        return new Cmyk(
+            (int) (100 * ($c - $k) / (1 - $k)),
+            (int) (100 * ($m - $k) / (1 - $k)),
+            (int) (100 * ($y - $k) / (1 - $k)),
+            (int) (100 * $k)
+        );
+    }
+
+    public function toGray() : Gray
+    {
+        return new Gray((int) (($this->red * 0.21 + $this->green * 0.71 + $this->blue * 0.07) / 2.55));
+    }
+}

+ 38 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/CompositeEye.php

@@ -0,0 +1,38 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Eye;
+
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Combines the style of two different eyes.
+ */
+final class CompositeEye implements EyeInterface
+{
+    /**
+     * @var EyeInterface
+     */
+    private $externalEye;
+
+    /**
+     * @var EyeInterface
+     */
+    private $internalEye;
+
+    public function __construct(EyeInterface $externalEye, EyeInterface $internalEye)
+    {
+        $this->externalEye = $externalEye;
+        $this->internalEye = $internalEye;
+    }
+
+    public function getExternalPath() : Path
+    {
+        return $this->externalEye->getExternalPath();
+    }
+
+    public function getInternalPath() : Path
+    {
+        return $this->internalEye->getInternalPath();
+    }
+}

+ 26 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/EyeInterface.php

@@ -0,0 +1,26 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Eye;
+
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Interface for describing the look of an eye.
+ */
+interface EyeInterface
+{
+    /**
+     * Returns the path of the external eye element.
+     *
+     * The path origin point (0, 0) must be anchored at the middle of the path.
+     */
+    public function getExternalPath() : Path;
+
+    /**
+     * Returns the path of the internal eye element.
+     *
+     * The path origin point (0, 0) must be anchored at the middle of the path.
+     */
+    public function getInternalPath() : Path;
+}

+ 54 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/ModuleEye.php

@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Eye;
+
+use BaconQrCode\Encoder\ByteMatrix;
+use BaconQrCode\Renderer\Module\ModuleInterface;
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Renders an eye based on a module renderer.
+ */
+final class ModuleEye implements EyeInterface
+{
+    /**
+     * @var ModuleInterface
+     */
+    private $module;
+
+    public function __construct(ModuleInterface $module)
+    {
+        $this->module = $module;
+    }
+
+    public function getExternalPath() : Path
+    {
+        $matrix = new ByteMatrix(7, 7);
+
+        for ($x = 0; $x < 7; ++$x) {
+            $matrix->set($x, 0, 1);
+            $matrix->set($x, 6, 1);
+        }
+
+        for ($y = 1; $y < 6; ++$y) {
+            $matrix->set(0, $y, 1);
+            $matrix->set(6, $y, 1);
+        }
+
+        return $this->module->createPath($matrix)->translate(-3.5, -3.5);
+    }
+
+    public function getInternalPath() : Path
+    {
+        $matrix = new ByteMatrix(3, 3);
+
+        for ($x = 0; $x < 3; ++$x) {
+            for ($y = 0; $y < 3; ++$y) {
+                $matrix->set($x, $y, 1);
+            }
+        }
+
+        return $this->module->createPath($matrix)->translate(-1.5, -1.5);
+    }
+}

+ 54 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/SimpleCircleEye.php

@@ -0,0 +1,54 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Eye;
+
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Renders the inner eye as a circle.
+ */
+final class SimpleCircleEye implements EyeInterface
+{
+    /**
+     * @var self|null
+     */
+    private static $instance;
+
+    private function __construct()
+    {
+    }
+
+    public static function instance() : self
+    {
+        return self::$instance ?: self::$instance = new self();
+    }
+
+    public function getExternalPath() : Path
+    {
+        return (new Path())
+            ->move(-3.5, -3.5)
+            ->line(3.5, -3.5)
+            ->line(3.5, 3.5)
+            ->line(-3.5, 3.5)
+            ->close()
+            ->move(-2.5, -2.5)
+            ->line(-2.5, 2.5)
+            ->line(2.5, 2.5)
+            ->line(2.5, -2.5)
+            ->close()
+        ;
+    }
+
+    public function getInternalPath() : Path
+    {
+        return (new Path())
+            ->move(1.5, 0)
+            ->ellipticArc(1.5, 1.5, 0., false, true, 0., 1.5)
+            ->ellipticArc(1.5, 1.5, 0., false, true, -1.5, 0.)
+            ->ellipticArc(1.5, 1.5, 0., false, true, 0., -1.5)
+            ->ellipticArc(1.5, 1.5, 0., false, true, 1.5, 0.)
+            ->close()
+        ;
+    }
+}

+ 53 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Eye/SquareEye.php

@@ -0,0 +1,53 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Eye;
+
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Renders the eyes in their default square shape.
+ */
+final class SquareEye implements EyeInterface
+{
+    /**
+     * @var self|null
+     */
+    private static $instance;
+
+    private function __construct()
+    {
+    }
+
+    public static function instance() : self
+    {
+        return self::$instance ?: self::$instance = new self();
+    }
+
+    public function getExternalPath() : Path
+    {
+        return (new Path())
+            ->move(-3.5, -3.5)
+            ->line(3.5, -3.5)
+            ->line(3.5, 3.5)
+            ->line(-3.5, 3.5)
+            ->close()
+            ->move(-2.5, -2.5)
+            ->line(-2.5, 2.5)
+            ->line(2.5, 2.5)
+            ->line(2.5, -2.5)
+            ->close()
+        ;
+    }
+
+    public function getInternalPath() : Path
+    {
+        return (new Path())
+            ->move(-1.5, -1.5)
+            ->line(1.5, -1.5)
+            ->line(1.5, 1.5)
+            ->line(-1.5, 1.5)
+            ->close()
+        ;
+    }
+}

+ 376 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/EpsImageBackEnd.php

@@ -0,0 +1,376 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Image;
+
+use BaconQrCode\Exception\RuntimeException;
+use BaconQrCode\Renderer\Color\Alpha;
+use BaconQrCode\Renderer\Color\Cmyk;
+use BaconQrCode\Renderer\Color\ColorInterface;
+use BaconQrCode\Renderer\Color\Gray;
+use BaconQrCode\Renderer\Color\Rgb;
+use BaconQrCode\Renderer\Path\Close;
+use BaconQrCode\Renderer\Path\Curve;
+use BaconQrCode\Renderer\Path\EllipticArc;
+use BaconQrCode\Renderer\Path\Line;
+use BaconQrCode\Renderer\Path\Move;
+use BaconQrCode\Renderer\Path\Path;
+use BaconQrCode\Renderer\RendererStyle\Gradient;
+use BaconQrCode\Renderer\RendererStyle\GradientType;
+
+final class EpsImageBackEnd implements ImageBackEndInterface
+{
+    private const PRECISION = 3;
+
+    /**
+     * @var string|null
+     */
+    private $eps;
+
+    public function new(int $size, ColorInterface $backgroundColor) : void
+    {
+        $this->eps = "%!PS-Adobe-3.0 EPSF-3.0\n"
+            . "%%Creator: BaconQrCode\n"
+            . sprintf("%%%%BoundingBox: 0 0 %d %d \n", $size, $size)
+            . "%%BeginProlog\n"
+            . "save\n"
+            . "50 dict begin\n"
+            . "/q { gsave } bind def\n"
+            . "/Q { grestore } bind def\n"
+            . "/s { scale } bind def\n"
+            . "/t { translate } bind def\n"
+            . "/r { rotate } bind def\n"
+            . "/n { newpath } bind def\n"
+            . "/m { moveto } bind def\n"
+            . "/l { lineto } bind def\n"
+            . "/c { curveto } bind def\n"
+            . "/z { closepath } bind def\n"
+            . "/f { eofill } bind def\n"
+            . "/rgb { setrgbcolor } bind def\n"
+            . "/cmyk { setcmykcolor } bind def\n"
+            . "/gray { setgray } bind def\n"
+            . "%%EndProlog\n"
+            . "1 -1 s\n"
+            . sprintf("0 -%d t\n", $size);
+
+        if ($backgroundColor instanceof Alpha && 0 === $backgroundColor->getAlpha()) {
+            return;
+        }
+
+        $this->eps .= wordwrap(
+            '0 0 m'
+            . sprintf(' %s 0 l', (string) $size)
+            . sprintf(' %s %s l', (string) $size, (string) $size)
+            . sprintf(' 0 %s l', (string) $size)
+            . ' z'
+            . ' ' .$this->getColorSetString($backgroundColor) . " f\n",
+            75,
+            "\n "
+        );
+    }
+
+    public function scale(float $size) : void
+    {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->eps .= sprintf("%1\$s %1\$s s\n", round($size, self::PRECISION));
+    }
+
+    public function translate(float $x, float $y) : void
+    {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->eps .= sprintf("%s %s t\n", round($x, self::PRECISION), round($y, self::PRECISION));
+    }
+
+    public function rotate(int $degrees) : void
+    {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->eps .= sprintf("%d r\n", $degrees);
+    }
+
+    public function push() : void
+    {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->eps .= "q\n";
+    }
+
+    public function pop() : void
+    {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->eps .= "Q\n";
+    }
+
+    public function drawPathWithColor(Path $path, ColorInterface $color) : void
+    {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $fromX = 0;
+        $fromY = 0;
+        $this->eps .= wordwrap(
+            'n '
+            . $this->drawPathOperations($path, $fromX, $fromY)
+            . ' ' . $this->getColorSetString($color) . " f\n",
+            75,
+            "\n "
+        );
+    }
+
+    public function drawPathWithGradient(
+        Path $path,
+        Gradient $gradient,
+        float $x,
+        float $y,
+        float $width,
+        float $height
+    ) : void {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $fromX = 0;
+        $fromY = 0;
+        $this->eps .= wordwrap(
+            'q n ' . $this->drawPathOperations($path, $fromX, $fromY) . "\n",
+            75,
+            "\n "
+        );
+
+        $this->createGradientFill($gradient, $x, $y, $width, $height);
+    }
+
+    public function done() : string
+    {
+        if (null === $this->eps) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->eps .= "%%TRAILER\nend restore\n%%EOF";
+        $blob = $this->eps;
+        $this->eps = null;
+
+        return $blob;
+    }
+
+    private function drawPathOperations(Iterable $ops, &$fromX, &$fromY) : string
+    {
+        $pathData = [];
+
+        foreach ($ops as $op) {
+            switch (true) {
+                case $op instanceof Move:
+                    $fromX = $toX = round($op->getX(), self::PRECISION);
+                    $fromY = $toY = round($op->getY(), self::PRECISION);
+                    $pathData[] = sprintf('%s %s m', $toX, $toY);
+                    break;
+
+                case $op instanceof Line:
+                    $fromX = $toX = round($op->getX(), self::PRECISION);
+                    $fromY = $toY = round($op->getY(), self::PRECISION);
+                    $pathData[] = sprintf('%s %s l', $toX, $toY);
+                    break;
+
+                case $op instanceof EllipticArc:
+                    $pathData[] = $this->drawPathOperations($op->toCurves($fromX, $fromY), $fromX, $fromY);
+                    break;
+
+                case $op instanceof Curve:
+                    $x1 = round($op->getX1(), self::PRECISION);
+                    $y1 = round($op->getY1(), self::PRECISION);
+                    $x2 = round($op->getX2(), self::PRECISION);
+                    $y2 = round($op->getY2(), self::PRECISION);
+                    $fromX = $x3 = round($op->getX3(), self::PRECISION);
+                    $fromY = $y3 = round($op->getY3(), self::PRECISION);
+                    $pathData[] = sprintf('%s %s %s %s %s %s c', $x1, $y1, $x2, $y2, $x3, $y3);
+                    break;
+
+                case $op instanceof Close:
+                    $pathData[] = 'z';
+                    break;
+
+                default:
+                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
+            }
+        }
+
+        return implode(' ', $pathData);
+    }
+
+    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : void
+    {
+        $startColor = $gradient->getStartColor();
+        $endColor = $gradient->getEndColor();
+
+        if ($startColor instanceof Alpha) {
+            $startColor = $startColor->getBaseColor();
+        }
+
+        $startColorType = get_class($startColor);
+
+        if (! in_array($startColorType, [Rgb::class, Cmyk::class, Gray::class])) {
+            $startColorType = Cmyk::class;
+            $startColor = $startColor->toCmyk();
+        }
+
+        if (get_class($endColor) !== $startColorType) {
+            switch ($startColorType) {
+                case Cmyk::class:
+                    $endColor = $endColor->toCmyk();
+                    break;
+
+                case Rgb::class:
+                    $endColor = $endColor->toRgb();
+                    break;
+
+                case Gray::class:
+                    $endColor = $endColor->toGray();
+                    break;
+            }
+        }
+
+        $this->eps .= "eoclip\n<<\n";
+
+        if ($gradient->getType() === GradientType::RADIAL()) {
+            $this->eps .= " /ShadingType 3\n";
+        } else {
+            $this->eps .= " /ShadingType 2\n";
+        }
+
+        $this->eps .= " /Extend [ true true ]\n"
+            . " /AntiAlias true\n";
+
+        switch ($startColorType) {
+            case Cmyk::class:
+                $this->eps .= " /ColorSpace /DeviceCMYK\n";
+                break;
+
+            case Rgb::class:
+                $this->eps .= " /ColorSpace /DeviceRGB\n";
+                break;
+
+            case Gray::class:
+                $this->eps .= " /ColorSpace /DeviceGray\n";
+                break;
+        }
+
+        switch ($gradient->getType()) {
+            case GradientType::HORIZONTAL():
+                $this->eps .= sprintf(
+                    " /Coords [ %s %s %s %s ]\n",
+                    round($x, self::PRECISION),
+                    round($y, self::PRECISION),
+                    round($x + $width, self::PRECISION),
+                    round($y, self::PRECISION)
+                );
+                break;
+
+            case GradientType::VERTICAL():
+                $this->eps .= sprintf(
+                    " /Coords [ %s %s %s %s ]\n",
+                    round($x, self::PRECISION),
+                    round($y, self::PRECISION),
+                    round($x, self::PRECISION),
+                    round($y + $height, self::PRECISION)
+                );
+                break;
+
+            case GradientType::DIAGONAL():
+                $this->eps .= sprintf(
+                    " /Coords [ %s %s %s %s ]\n",
+                    round($x, self::PRECISION),
+                    round($y, self::PRECISION),
+                    round($x + $width, self::PRECISION),
+                    round($y + $height, self::PRECISION)
+                );
+                break;
+
+            case GradientType::INVERSE_DIAGONAL():
+                $this->eps .= sprintf(
+                    " /Coords [ %s %s %s %s ]\n",
+                    round($x, self::PRECISION),
+                    round($y + $height, self::PRECISION),
+                    round($x + $width, self::PRECISION),
+                    round($y, self::PRECISION)
+                );
+                break;
+
+            case GradientType::RADIAL():
+                $centerX = ($x + $width) / 2;
+                $centerY = ($y + $height) / 2;
+
+                $this->eps .= sprintf(
+                    " /Coords [ %s %s 0 %s %s %s ]\n",
+                    round($centerX, self::PRECISION),
+                    round($centerY, self::PRECISION),
+                    round($centerX, self::PRECISION),
+                    round($centerY, self::PRECISION),
+                    round(max($width, $height) / 2, self::PRECISION)
+                );
+                break;
+        }
+
+        $this->eps .= " /Function\n"
+            . " <<\n"
+            . "  /FunctionType 2\n"
+            . "  /Domain [ 0 1 ]\n"
+            . sprintf("  /C0 [ %s ]\n", $this->getColorString($startColor))
+            . sprintf("  /C1 [ %s ]\n", $this->getColorString($endColor))
+            . "  /N 1\n"
+            . " >>\n>>\nshfill\nQ\n";
+    }
+
+    private function getColorSetString(ColorInterface $color) : string
+    {
+        if ($color instanceof Rgb) {
+            return $this->getColorString($color) . ' rgb';
+        }
+
+        if ($color instanceof Cmyk) {
+            return $this->getColorString($color) . ' cmyk';
+        }
+
+        if ($color instanceof Gray) {
+            return $this->getColorString($color) . ' gray';
+        }
+
+        return $this->getColorSetString($color->toCmyk());
+    }
+
+    private function getColorString(ColorInterface $color) : string
+    {
+        if ($color instanceof Rgb) {
+            return sprintf('%s %s %s', $color->getRed() / 255, $color->getGreen() / 255, $color->getBlue() / 255);
+        }
+
+        if ($color instanceof Cmyk) {
+            return sprintf(
+                '%s %s %s %s',
+                $color->getCyan() / 100,
+                $color->getMagenta() / 100,
+                $color->getYellow() / 100,
+                $color->getBlack() / 100
+            );
+        }
+
+        if ($color instanceof Gray) {
+            return sprintf('%s', $color->getGray() / 100);
+        }
+
+        return $this->getColorString($color->toCmyk());
+    }
+}

+ 87 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/ImageBackEndInterface.php

@@ -0,0 +1,87 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Image;
+
+use BaconQrCode\Exception\RuntimeException;
+use BaconQrCode\Renderer\Color\ColorInterface;
+use BaconQrCode\Renderer\Path\Path;
+use BaconQrCode\Renderer\RendererStyle\Gradient;
+
+/**
+ * Interface for back ends able to to produce path based images.
+ */
+interface ImageBackEndInterface
+{
+    /**
+     * Starts a new image.
+     *
+     * If a previous image was already started, previous data get erased.
+     */
+    public function new(int $size, ColorInterface $backgroundColor) : void;
+
+    /**
+     * Transforms all following drawing operation coordinates by scaling them by a given factor.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function scale(float $size) : void;
+
+    /**
+     * Transforms all following drawing operation coordinates by translating them by a given amount.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function translate(float $x, float $y) : void;
+
+    /**
+     * Transforms all following drawing operation coordinates by rotating them by a given amount.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function rotate(int $degrees) : void;
+
+    /**
+     * Pushes the current coordinate transformation onto a stack.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function push() : void;
+
+    /**
+     * Pops the last coordinate transformation from a stack.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function pop() : void;
+
+    /**
+     * Draws a path with a given color.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function drawPathWithColor(Path $path, ColorInterface $color) : void;
+
+    /**
+     * Draws a path with a given gradient which spans the box described by the position and size.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function drawPathWithGradient(
+        Path $path,
+        Gradient $gradient,
+        float $x,
+        float $y,
+        float $width,
+        float $height
+    ) : void;
+
+    /**
+     * Ends the image drawing operation and returns the resulting blob.
+     *
+     * This should reset the state of the back end and thus this method should only be callable once per image.
+     *
+     * @throws RuntimeException if no image was started yet.
+     */
+    public function done() : string;
+}

+ 336 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/ImagickImageBackEnd.php

@@ -0,0 +1,336 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Image;
+
+use BaconQrCode\Exception\RuntimeException;
+use BaconQrCode\Renderer\Color\Alpha;
+use BaconQrCode\Renderer\Color\Cmyk;
+use BaconQrCode\Renderer\Color\ColorInterface;
+use BaconQrCode\Renderer\Color\Gray;
+use BaconQrCode\Renderer\Color\Rgb;
+use BaconQrCode\Renderer\Path\Close;
+use BaconQrCode\Renderer\Path\Curve;
+use BaconQrCode\Renderer\Path\EllipticArc;
+use BaconQrCode\Renderer\Path\Line;
+use BaconQrCode\Renderer\Path\Move;
+use BaconQrCode\Renderer\Path\Path;
+use BaconQrCode\Renderer\RendererStyle\Gradient;
+use BaconQrCode\Renderer\RendererStyle\GradientType;
+use Imagick;
+use ImagickDraw;
+use ImagickPixel;
+
+final class ImagickImageBackEnd implements ImageBackEndInterface
+{
+    /**
+     * @var string
+     */
+    private $imageFormat;
+
+    /**
+     * @var int
+     */
+    private $compressionQuality;
+
+    /**
+     * @var Imagick|null
+     */
+    private $image;
+
+    /**
+     * @var ImagickDraw|null
+     */
+    private $draw;
+
+    /**
+     * @var int|null
+     */
+    private $gradientCount;
+
+    /**
+     * @var TransformationMatrix[]|null
+     */
+    private $matrices;
+
+    /**
+     * @var int|null
+     */
+    private $matrixIndex;
+
+    public function __construct(string $imageFormat = 'png', int $compressionQuality = 100)
+    {
+        if (! class_exists(Imagick::class)) {
+            throw new RuntimeException('You need to install the imagick extension to use this back end');
+        }
+
+        $this->imageFormat = $imageFormat;
+        $this->compressionQuality = $compressionQuality;
+    }
+
+    public function new(int $size, ColorInterface $backgroundColor) : void
+    {
+        $this->image = new Imagick();
+        $this->image->newImage($size, $size, $this->getColorPixel($backgroundColor));
+        $this->image->setImageFormat($this->imageFormat);
+        $this->image->setCompressionQuality($this->compressionQuality);
+        $this->draw = new ImagickDraw();
+        $this->gradientCount = 0;
+        $this->matrices = [new TransformationMatrix()];
+        $this->matrixIndex = 0;
+    }
+
+    public function scale(float $size) : void
+    {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->draw->scale($size, $size);
+        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
+            ->multiply(TransformationMatrix::scale($size));
+    }
+
+    public function translate(float $x, float $y) : void
+    {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->draw->translate($x, $y);
+        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
+            ->multiply(TransformationMatrix::translate($x, $y));
+    }
+
+    public function rotate(int $degrees) : void
+    {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->draw->rotate($degrees);
+        $this->matrices[$this->matrixIndex] = $this->matrices[$this->matrixIndex]
+            ->multiply(TransformationMatrix::rotate($degrees));
+    }
+
+    public function push() : void
+    {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->draw->push();
+        $this->matrices[++$this->matrixIndex] = $this->matrices[$this->matrixIndex - 1];
+    }
+
+    public function pop() : void
+    {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->draw->pop();
+        unset($this->matrices[$this->matrixIndex--]);
+    }
+
+    public function drawPathWithColor(Path $path, ColorInterface $color) : void
+    {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->draw->setFillColor($this->getColorPixel($color));
+        $this->drawPath($path);
+    }
+
+    public function drawPathWithGradient(
+        Path $path,
+        Gradient $gradient,
+        float $x,
+        float $y,
+        float $width,
+        float $height
+    ) : void {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->draw->setFillPatternURL('#' . $this->createGradientFill($gradient, $x, $y, $width, $height));
+        $this->drawPath($path);
+    }
+
+    public function done() : string
+    {
+        if (null === $this->draw) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->image->drawImage($this->draw);
+        $blob = $this->image->getImageBlob();
+        $this->draw->clear();
+        $this->image->clear();
+        $this->draw = null;
+        $this->image = null;
+        $this->gradientCount = null;
+
+        return $blob;
+    }
+
+    private function drawPath(Path $path) : void
+    {
+        $this->draw->pathStart();
+
+        foreach ($path as $op) {
+            switch (true) {
+                case $op instanceof Move:
+                    $this->draw->pathMoveToAbsolute($op->getX(), $op->getY());
+                    break;
+
+                case $op instanceof Line:
+                    $this->draw->pathLineToAbsolute($op->getX(), $op->getY());
+                    break;
+
+                case $op instanceof EllipticArc:
+                    $this->draw->pathEllipticArcAbsolute(
+                        $op->getXRadius(),
+                        $op->getYRadius(),
+                        $op->getXAxisAngle(),
+                        $op->isLargeArc(),
+                        $op->isSweep(),
+                        $op->getX(),
+                        $op->getY()
+                    );
+                    break;
+
+                case $op instanceof Curve:
+                    $this->draw->pathCurveToAbsolute(
+                        $op->getX1(),
+                        $op->getY1(),
+                        $op->getX2(),
+                        $op->getY2(),
+                        $op->getX3(),
+                        $op->getY3()
+                    );
+                    break;
+
+                case $op instanceof Close:
+                    $this->draw->pathClose();
+                    break;
+
+                default:
+                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
+            }
+        }
+
+        $this->draw->pathFinish();
+    }
+
+    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
+    {
+        list($width, $height) = $this->matrices[$this->matrixIndex]->apply($width, $height);
+
+        $startColor = $this->getColorPixel($gradient->getStartColor())->getColorAsString();
+        $endColor = $this->getColorPixel($gradient->getEndColor())->getColorAsString();
+        $gradientImage = new Imagick();
+
+        switch ($gradient->getType()) {
+            case GradientType::HORIZONTAL():
+                $gradientImage->newPseudoImage((int) $height, (int) $width, sprintf(
+                    'gradient:%s-%s',
+                    $startColor,
+                    $endColor
+                ));
+                $gradientImage->rotateImage('transparent', -90);
+                break;
+
+            case GradientType::VERTICAL():
+                $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(
+                    'gradient:%s-%s',
+                    $startColor,
+                    $endColor
+                ));
+                break;
+
+            case GradientType::DIAGONAL():
+            case GradientType::INVERSE_DIAGONAL():
+                $gradientImage->newPseudoImage((int) ($width * sqrt(2)), (int) ($height * sqrt(2)), sprintf(
+                    'gradient:%s-%s',
+                    $startColor,
+                    $endColor
+                ));
+
+                if (GradientType::DIAGONAL() === $gradient->getType()) {
+                    $gradientImage->rotateImage('transparent', -45);
+                } else {
+                    $gradientImage->rotateImage('transparent', -135);
+                }
+
+                $rotatedWidth = $gradientImage->getImageWidth();
+                $rotatedHeight = $gradientImage->getImageHeight();
+
+                $gradientImage->setImagePage($rotatedWidth, $rotatedHeight, 0, 0);
+                $gradientImage->cropImage(
+                    intdiv($rotatedWidth, 2) - 2,
+                    intdiv($rotatedHeight, 2) - 2,
+                    intdiv($rotatedWidth, 4) + 1,
+                    intdiv($rotatedWidth, 4) + 1
+                );
+                break;
+
+            case GradientType::RADIAL():
+                $gradientImage->newPseudoImage((int) $width, (int) $height, sprintf(
+                    'radial-gradient:%s-%s',
+                    $startColor,
+                    $endColor
+                ));
+                break;
+        }
+
+        $id = sprintf('g%d', ++$this->gradientCount);
+        $this->draw->pushPattern($id, 0, 0, $width, $height);
+        $this->draw->composite(Imagick::COMPOSITE_COPY, 0, 0, $width, $height, $gradientImage);
+        $this->draw->popPattern();
+        return $id;
+    }
+
+    private function getColorPixel(ColorInterface $color) : ImagickPixel
+    {
+        $alpha = 100;
+
+        if ($color instanceof Alpha) {
+            $alpha = $color->getAlpha();
+            $color = $color->getBaseColor();
+        }
+
+        if ($color instanceof Rgb) {
+            return new ImagickPixel(sprintf(
+                'rgba(%d, %d, %d, %F)',
+                $color->getRed(),
+                $color->getGreen(),
+                $color->getBlue(),
+                $alpha / 100
+            ));
+        }
+
+        if ($color instanceof Cmyk) {
+            return new ImagickPixel(sprintf(
+                'cmyka(%d, %d, %d, %d, %F)',
+                $color->getCyan(),
+                $color->getMagenta(),
+                $color->getYellow(),
+                $color->getBlack(),
+                $alpha / 100
+            ));
+        }
+
+        if ($color instanceof Gray) {
+            return new ImagickPixel(sprintf(
+                'graya(%d%%, %F)',
+                $color->getGray(),
+                $alpha / 100
+            ));
+        }
+
+        return $this->getColorPixel(new Alpha($alpha, $color->toRgb()));
+    }
+}

+ 369 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/SvgImageBackEnd.php

@@ -0,0 +1,369 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Image;
+
+use BaconQrCode\Exception\RuntimeException;
+use BaconQrCode\Renderer\Color\Alpha;
+use BaconQrCode\Renderer\Color\ColorInterface;
+use BaconQrCode\Renderer\Path\Close;
+use BaconQrCode\Renderer\Path\Curve;
+use BaconQrCode\Renderer\Path\EllipticArc;
+use BaconQrCode\Renderer\Path\Line;
+use BaconQrCode\Renderer\Path\Move;
+use BaconQrCode\Renderer\Path\Path;
+use BaconQrCode\Renderer\RendererStyle\Gradient;
+use BaconQrCode\Renderer\RendererStyle\GradientType;
+use XMLWriter;
+
+final class SvgImageBackEnd implements ImageBackEndInterface
+{
+    private const PRECISION = 3;
+
+    /**
+     * @var XMLWriter|null
+     */
+    private $xmlWriter;
+
+    /**
+     * @var int[]|null
+     */
+    private $stack;
+
+    /**
+     * @var int|null
+     */
+    private $currentStack;
+
+    /**
+     * @var int|null
+     */
+    private $gradientCount;
+
+    public function __construct()
+    {
+        if (! class_exists(XMLWriter::class)) {
+            throw new RuntimeException('You need to install the libxml extension to use this back end');
+        }
+    }
+
+    public function new(int $size, ColorInterface $backgroundColor) : void
+    {
+        $this->xmlWriter = new XMLWriter();
+        $this->xmlWriter->openMemory();
+
+        $this->xmlWriter->startDocument('1.0', 'UTF-8');
+        $this->xmlWriter->startElement('svg');
+        $this->xmlWriter->writeAttribute('xmlns', 'http://www.w3.org/2000/svg');
+        $this->xmlWriter->writeAttribute('version', '1.1');
+        $this->xmlWriter->writeAttribute('width', (string) $size);
+        $this->xmlWriter->writeAttribute('height', (string) $size);
+        $this->xmlWriter->writeAttribute('viewBox', '0 0 '. $size . ' ' . $size);
+
+        $this->gradientCount = 0;
+        $this->currentStack = 0;
+        $this->stack[0] = 0;
+
+        $alpha = 1;
+
+        if ($backgroundColor instanceof Alpha) {
+            $alpha = $backgroundColor->getAlpha() / 100;
+        }
+
+        if (0 === $alpha) {
+            return;
+        }
+
+        $this->xmlWriter->startElement('rect');
+        $this->xmlWriter->writeAttribute('x', '0');
+        $this->xmlWriter->writeAttribute('y', '0');
+        $this->xmlWriter->writeAttribute('width', (string) $size);
+        $this->xmlWriter->writeAttribute('height', (string) $size);
+        $this->xmlWriter->writeAttribute('fill', $this->getColorString($backgroundColor));
+
+        if ($alpha < 1) {
+            $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
+        }
+
+        $this->xmlWriter->endElement();
+    }
+
+    public function scale(float $size) : void
+    {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->xmlWriter->startElement('g');
+        $this->xmlWriter->writeAttribute(
+            'transform',
+            sprintf('scale(%s)', round($size, self::PRECISION))
+        );
+        ++$this->stack[$this->currentStack];
+    }
+
+    public function translate(float $x, float $y) : void
+    {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->xmlWriter->startElement('g');
+        $this->xmlWriter->writeAttribute(
+            'transform',
+            sprintf('translate(%s,%s)', round($x, self::PRECISION), round($y, self::PRECISION))
+        );
+        ++$this->stack[$this->currentStack];
+    }
+
+    public function rotate(int $degrees) : void
+    {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->xmlWriter->startElement('g');
+        $this->xmlWriter->writeAttribute('transform', sprintf('rotate(%d)', $degrees));
+        ++$this->stack[$this->currentStack];
+    }
+
+    public function push() : void
+    {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $this->xmlWriter->startElement('g');
+        $this->stack[] = 1;
+        ++$this->currentStack;
+    }
+
+    public function pop() : void
+    {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        for ($i = 0; $i < $this->stack[$this->currentStack]; ++$i) {
+            $this->xmlWriter->endElement();
+        }
+
+        array_pop($this->stack);
+        --$this->currentStack;
+    }
+
+    public function drawPathWithColor(Path $path, ColorInterface $color) : void
+    {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $alpha = 1;
+
+        if ($color instanceof Alpha) {
+            $alpha = $color->getAlpha() / 100;
+        }
+
+        $this->startPathElement($path);
+        $this->xmlWriter->writeAttribute('fill', $this->getColorString($color));
+
+        if ($alpha < 1) {
+            $this->xmlWriter->writeAttribute('fill-opacity', (string) $alpha);
+        }
+
+        $this->xmlWriter->endElement();
+    }
+
+    public function drawPathWithGradient(
+        Path $path,
+        Gradient $gradient,
+        float $x,
+        float $y,
+        float $width,
+        float $height
+    ) : void {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        $gradientId = $this->createGradientFill($gradient, $x, $y, $width, $height);
+        $this->startPathElement($path);
+        $this->xmlWriter->writeAttribute('fill', 'url(#' . $gradientId . ')');
+        $this->xmlWriter->endElement();
+    }
+
+    public function done() : string
+    {
+        if (null === $this->xmlWriter) {
+            throw new RuntimeException('No image has been started');
+        }
+
+        foreach ($this->stack as $openElements) {
+            for ($i = $openElements; $i > 0; --$i) {
+                $this->xmlWriter->endElement();
+            }
+        }
+
+        $this->xmlWriter->endDocument();
+        $blob = $this->xmlWriter->outputMemory(true);
+        $this->xmlWriter = null;
+        $this->stack = null;
+        $this->currentStack = null;
+        $this->gradientCount = null;
+
+        return $blob;
+    }
+
+    private function startPathElement(Path $path) : void
+    {
+        $pathData = [];
+
+        foreach ($path as $op) {
+            switch (true) {
+                case $op instanceof Move:
+                    $pathData[] = sprintf(
+                        'M%s %s',
+                        round($op->getX(), self::PRECISION),
+                        round($op->getY(), self::PRECISION)
+                    );
+                    break;
+
+                case $op instanceof Line:
+                    $pathData[] = sprintf(
+                        'L%s %s',
+                        round($op->getX(), self::PRECISION),
+                        round($op->getY(), self::PRECISION)
+                    );
+                    break;
+
+                case $op instanceof EllipticArc:
+                    $pathData[] = sprintf(
+                        'A%s %s %s %u %u %s %s',
+                        round($op->getXRadius(), self::PRECISION),
+                        round($op->getYRadius(), self::PRECISION),
+                        round($op->getXAxisAngle(), self::PRECISION),
+                        $op->isLargeArc(),
+                        $op->isSweep(),
+                        round($op->getX(), self::PRECISION),
+                        round($op->getY(), self::PRECISION)
+                    );
+                    break;
+
+                case $op instanceof Curve:
+                    $pathData[] = sprintf(
+                        'C%s %s %s %s %s %s',
+                        round($op->getX1(), self::PRECISION),
+                        round($op->getY1(), self::PRECISION),
+                        round($op->getX2(), self::PRECISION),
+                        round($op->getY2(), self::PRECISION),
+                        round($op->getX3(), self::PRECISION),
+                        round($op->getY3(), self::PRECISION)
+                    );
+                    break;
+
+                case $op instanceof Close:
+                    $pathData[] = 'Z';
+                    break;
+
+                default:
+                    throw new RuntimeException('Unexpected draw operation: ' . get_class($op));
+            }
+        }
+
+        $this->xmlWriter->startElement('path');
+        $this->xmlWriter->writeAttribute('fill-rule', 'evenodd');
+        $this->xmlWriter->writeAttribute('d', implode('', $pathData));
+    }
+
+    private function createGradientFill(Gradient $gradient, float $x, float $y, float $width, float $height) : string
+    {
+        $this->xmlWriter->startElement('defs');
+
+        $startColor = $gradient->getStartColor();
+        $endColor = $gradient->getEndColor();
+
+        if ($gradient->getType() === GradientType::RADIAL()) {
+            $this->xmlWriter->startElement('radialGradient');
+        } else {
+            $this->xmlWriter->startElement('linearGradient');
+        }
+
+        $this->xmlWriter->writeAttribute('gradientUnits', 'userSpaceOnUse');
+
+        switch ($gradient->getType()) {
+            case GradientType::HORIZONTAL():
+                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
+                $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
+                break;
+
+            case GradientType::VERTICAL():
+                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
+                $this->xmlWriter->writeAttribute('x2', (string) round($x, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
+                break;
+
+            case GradientType::DIAGONAL():
+                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y1', (string) round($y, self::PRECISION));
+                $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y2', (string) round($y + $height, self::PRECISION));
+                break;
+
+            case GradientType::INVERSE_DIAGONAL():
+                $this->xmlWriter->writeAttribute('x1', (string) round($x, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y1', (string) round($y + $height, self::PRECISION));
+                $this->xmlWriter->writeAttribute('x2', (string) round($x + $width, self::PRECISION));
+                $this->xmlWriter->writeAttribute('y2', (string) round($y, self::PRECISION));
+                break;
+
+            case GradientType::RADIAL():
+                $this->xmlWriter->writeAttribute('cx', (string) round(($x + $width) / 2, self::PRECISION));
+                $this->xmlWriter->writeAttribute('cy', (string) round(($y + $height) / 2, self::PRECISION));
+                $this->xmlWriter->writeAttribute('r', (string) round(max($width, $height) / 2, self::PRECISION));
+                break;
+        }
+
+        $id = sprintf('g%d', ++$this->gradientCount);
+        $this->xmlWriter->writeAttribute('id', $id);
+
+        $this->xmlWriter->startElement('stop');
+        $this->xmlWriter->writeAttribute('offset', '0%');
+        $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($startColor));
+
+        if ($startColor instanceof Alpha) {
+            $this->xmlWriter->writeAttribute('stop-opacity', (string) $startColor->getAlpha());
+        }
+
+        $this->xmlWriter->endElement();
+
+        $this->xmlWriter->startElement('stop');
+        $this->xmlWriter->writeAttribute('offset', '100%');
+        $this->xmlWriter->writeAttribute('stop-color', $this->getColorString($endColor));
+
+        if ($endColor instanceof Alpha) {
+            $this->xmlWriter->writeAttribute('stop-opacity', (string) $endColor->getAlpha());
+        }
+
+        $this->xmlWriter->endElement();
+
+        $this->xmlWriter->endElement();
+        $this->xmlWriter->endElement();
+
+        return $id;
+    }
+
+    private function getColorString(ColorInterface $color) : string
+    {
+        $color = $color->toRgb();
+
+        return sprintf(
+            '#%02x%02x%02x',
+            $color->getRed(),
+            $color->getGreen(),
+            $color->getBlue()
+        );
+    }
+}

+ 68 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Image/TransformationMatrix.php

@@ -0,0 +1,68 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Image;
+
+final class TransformationMatrix
+{
+    /**
+     * @var float[]
+     */
+    private $values;
+
+    public function __construct()
+    {
+        $this->values = [1, 0, 0, 1, 0, 0];
+    }
+
+    public function multiply(self $other) : self
+    {
+        $matrix = new self();
+        $matrix->values[0] = $this->values[0] * $other->values[0] + $this->values[2] * $other->values[1];
+        $matrix->values[1] = $this->values[1] * $other->values[0] + $this->values[3] * $other->values[1];
+        $matrix->values[2] = $this->values[0] * $other->values[2] + $this->values[2] * $other->values[3];
+        $matrix->values[3] = $this->values[1] * $other->values[2] + $this->values[3] * $other->values[3];
+        $matrix->values[4] = $this->values[0] * $other->values[4] + $this->values[2] * $other->values[5]
+            + $this->values[4];
+        $matrix->values[5] = $this->values[1] * $other->values[4] + $this->values[3] * $other->values[5]
+            + $this->values[5];
+
+        return $matrix;
+    }
+
+    public static function scale(float $size) : self
+    {
+        $matrix = new self();
+        $matrix->values = [$size, 0, 0, $size, 0, 0];
+        return $matrix;
+    }
+
+    public static function translate(float $x, float $y) : self
+    {
+        $matrix = new self();
+        $matrix->values = [1, 0, 0, 1, $x, $y];
+        return $matrix;
+    }
+
+    public static function rotate(int $degrees) : self
+    {
+        $matrix = new self();
+        $rad = deg2rad($degrees);
+        $matrix->values = [cos($rad), sin($rad), -sin($rad), cos($rad), 0, 0];
+        return $matrix;
+    }
+
+
+    /**
+     * Applies this matrix onto a point and returns the resulting viewport point.
+     *
+     * @return float[]
+     */
+    public function apply(float $x, float $y) : array
+    {
+        return [
+            $x * $this->values[0] + $y * $this->values[2] + $this->values[4],
+            $x * $this->values[1] + $y * $this->values[3] + $this->values[5],
+        ];
+    }
+}

+ 152 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/ImageRenderer.php

@@ -0,0 +1,152 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer;
+
+use BaconQrCode\Encoder\MatrixUtil;
+use BaconQrCode\Encoder\QrCode;
+use BaconQrCode\Exception\InvalidArgumentException;
+use BaconQrCode\Renderer\Image\ImageBackEndInterface;
+use BaconQrCode\Renderer\Path\Path;
+use BaconQrCode\Renderer\RendererStyle\EyeFill;
+use BaconQrCode\Renderer\RendererStyle\RendererStyle;
+
+final class ImageRenderer implements RendererInterface
+{
+    /**
+     * @var RendererStyle
+     */
+    private $rendererStyle;
+
+    /**
+     * @var ImageBackEndInterface
+     */
+    private $imageBackEnd;
+
+    public function __construct(RendererStyle $rendererStyle, ImageBackEndInterface $imageBackEnd)
+    {
+        $this->rendererStyle = $rendererStyle;
+        $this->imageBackEnd = $imageBackEnd;
+    }
+
+    /**
+     * @throws InvalidArgumentException if matrix width doesn't match height
+     */
+    public function render(QrCode $qrCode) : string
+    {
+        $size = $this->rendererStyle->getSize();
+        $margin = $this->rendererStyle->getMargin();
+        $matrix = $qrCode->getMatrix();
+        $matrixSize = $matrix->getWidth();
+
+        if ($matrixSize !== $matrix->getHeight()) {
+            throw new InvalidArgumentException('Matrix must have the same width and height');
+        }
+
+        $totalSize = $matrixSize + ($margin * 2);
+        $moduleSize = $size / $totalSize;
+        $fill = $this->rendererStyle->getFill();
+
+        $this->imageBackEnd->new($size, $fill->getBackgroundColor());
+        $this->imageBackEnd->scale((float) $moduleSize);
+        $this->imageBackEnd->translate((float) $margin, (float) $margin);
+
+        $module = $this->rendererStyle->getModule();
+        $moduleMatrix = clone $matrix;
+        MatrixUtil::removePositionDetectionPatterns($moduleMatrix);
+        $modulePath = $this->drawEyes($matrixSize, $module->createPath($moduleMatrix));
+
+        if ($fill->hasGradientFill()) {
+            $this->imageBackEnd->drawPathWithGradient(
+                $modulePath,
+                $fill->getForegroundGradient(),
+                0,
+                0,
+                $matrixSize,
+                $matrixSize
+            );
+        } else {
+            $this->imageBackEnd->drawPathWithColor($modulePath, $fill->getForegroundColor());
+        }
+
+        return $this->imageBackEnd->done();
+    }
+
+    private function drawEyes(int $matrixSize, Path $modulePath) : Path
+    {
+        $fill = $this->rendererStyle->getFill();
+
+        $eye = $this->rendererStyle->getEye();
+        $externalPath = $eye->getExternalPath();
+        $internalPath = $eye->getInternalPath();
+
+        $modulePath = $this->drawEye(
+            $externalPath,
+            $internalPath,
+            $fill->getTopLeftEyeFill(),
+            3.5,
+            3.5,
+            0,
+            $modulePath
+        );
+        $modulePath = $this->drawEye(
+            $externalPath,
+            $internalPath,
+            $fill->getTopRightEyeFill(),
+            $matrixSize - 3.5,
+            3.5,
+            90,
+            $modulePath
+        );
+        $modulePath = $this->drawEye(
+            $externalPath,
+            $internalPath,
+            $fill->getBottomLeftEyeFill(),
+            3.5,
+            $matrixSize - 3.5,
+            -90,
+            $modulePath
+        );
+
+        return $modulePath;
+    }
+
+    private function drawEye(
+        Path $externalPath,
+        Path $internalPath,
+        EyeFill $fill,
+        float $xTranslation,
+        float $yTranslation,
+        int $rotation,
+        Path $modulePath
+    ) : Path {
+        if ($fill->inheritsBothColors()) {
+            return $modulePath
+                ->append($externalPath->translate($xTranslation, $yTranslation))
+                ->append($internalPath->translate($xTranslation, $yTranslation));
+        }
+
+        $this->imageBackEnd->push();
+        $this->imageBackEnd->translate($xTranslation, $yTranslation);
+
+        if (0 !== $rotation) {
+            $this->imageBackEnd->rotate($rotation);
+        }
+
+        if ($fill->inheritsExternalColor()) {
+            $modulePath = $modulePath->append($externalPath->translate($xTranslation, $yTranslation));
+        } else {
+            $this->imageBackEnd->drawPathWithColor($externalPath, $fill->getExternalColor());
+        }
+
+        if ($fill->inheritsInternalColor()) {
+            $modulePath = $modulePath->append($internalPath->translate($xTranslation, $yTranslation));
+        } else {
+            $this->imageBackEnd->drawPathWithColor($internalPath, $fill->getInternalColor());
+        }
+
+        $this->imageBackEnd->pop();
+
+        return $modulePath;
+    }
+}

+ 63 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/DotsModule.php

@@ -0,0 +1,63 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Module;
+
+use BaconQrCode\Encoder\ByteMatrix;
+use BaconQrCode\Exception\InvalidArgumentException;
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Renders individual modules as dots.
+ */
+final class DotsModule implements ModuleInterface
+{
+    public const LARGE = 1;
+    public const MEDIUM = .8;
+    public const SMALL = .6;
+
+    /**
+     * @var float
+     */
+    private $size;
+
+    public function __construct(float $size)
+    {
+        if ($size <= 0 || $size > 1) {
+            throw new InvalidArgumentException('Size must between 0 (exclusive) and 1 (inclusive)');
+        }
+
+        $this->size = $size;
+    }
+
+    public function createPath(ByteMatrix $matrix) : Path
+    {
+        $width = $matrix->getWidth();
+        $height = $matrix->getHeight();
+        $path = new Path();
+        $halfSize = $this->size / 2;
+        $margin = (1 - $this->size) / 2;
+
+        for ($y = 0; $y < $height; ++$y) {
+            for ($x = 0; $x < $width; ++$x) {
+                if (! $matrix->get($x, $y)) {
+                    continue;
+                }
+
+                $pathX = $x + $margin;
+                $pathY = $y + $margin;
+
+                $path = $path
+                    ->move($pathX + $this->size, $pathY + $halfSize)
+                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY + $this->size)
+                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX, $pathY + $halfSize)
+                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $halfSize, $pathY)
+                    ->ellipticArc($halfSize, $halfSize, 0, false, true, $pathX + $this->size, $pathY + $halfSize)
+                    ->close()
+                ;
+            }
+        }
+
+        return $path;
+    }
+}

+ 100 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/Edge.php

@@ -0,0 +1,100 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Module\EdgeIterator;
+
+final class Edge
+{
+    /**
+     * @var bool
+     */
+    private $positive;
+
+    /**
+     * @var array<int[]>
+     */
+    private $points = [];
+
+    /**
+     * @var array<int[]>|null
+     */
+    private $simplifiedPoints;
+
+    /**
+     * @var int
+     */
+    private $minX = PHP_INT_MAX;
+
+    /**
+     * @var int
+     */
+    private $minY = PHP_INT_MAX;
+
+    /**
+     * @var int
+     */
+    private $maxX = -1;
+
+    /**
+     * @var int
+     */
+    private $maxY = -1;
+
+    public function __construct(bool $positive)
+    {
+        $this->positive = $positive;
+    }
+
+    public function addPoint(int $x, int $y) : void
+    {
+        $this->points[] = [$x, $y];
+        $this->minX = min($this->minX, $x);
+        $this->minY = min($this->minY, $y);
+        $this->maxX = max($this->maxX, $x);
+        $this->maxY = max($this->maxY, $y);
+    }
+
+    public function isPositive() : bool
+    {
+        return $this->positive;
+    }
+
+    /**
+     * @return array<int[]>
+     */
+    public function getPoints() : array
+    {
+        return $this->points;
+    }
+
+    public function getMaxX() : int
+    {
+        return $this->maxX;
+    }
+
+    public function getSimplifiedPoints() : array
+    {
+        if (null !== $this->simplifiedPoints) {
+            return $this->simplifiedPoints;
+        }
+
+        $points = [];
+        $length = count($this->points);
+
+        for ($i = 0; $i < $length; ++$i) {
+            $previousPoint = $this->points[(0 === $i ? $length : $i) - 1];
+            $nextPoint = $this->points[($length - 1 === $i ? -1 : $i) + 1];
+            $currentPoint = $this->points[$i];
+
+            if (($previousPoint[0] === $currentPoint[0] && $currentPoint[0] === $nextPoint[0])
+                || ($previousPoint[1] === $currentPoint[1] && $currentPoint[1] === $nextPoint[1])
+            ) {
+                continue;
+            }
+
+            $points[] = $currentPoint;
+        }
+
+        return $this->simplifiedPoints = $points;
+    }
+}

+ 169 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/EdgeIterator/EdgeIterator.php

@@ -0,0 +1,169 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Module\EdgeIterator;
+
+use BaconQrCode\Encoder\ByteMatrix;
+use IteratorAggregate;
+use Traversable;
+
+/**
+ * Edge iterator based on potrace.
+ */
+final class EdgeIterator implements IteratorAggregate
+{
+    /**
+     * @var int[]
+     */
+    private $bytes = [];
+
+    /**
+     * @var int
+     */
+    private $size;
+
+    /**
+     * @var int
+     */
+    private $width;
+
+    /**
+     * @var int
+     */
+    private $height;
+
+    public function __construct(ByteMatrix $matrix)
+    {
+        $this->bytes = iterator_to_array($matrix->getBytes());
+        $this->size = count($this->bytes);
+        $this->width = $matrix->getWidth();
+        $this->height = $matrix->getHeight();
+    }
+
+    /**
+     * @return Traversable<Edge>
+     */
+    public function getIterator() : Traversable
+    {
+        $originalBytes = $this->bytes;
+        $point = $this->findNext(0, 0);
+
+        while (null !== $point) {
+            $edge = $this->findEdge($point[0], $point[1]);
+            $this->xorEdge($edge);
+
+            yield $edge;
+
+            $point = $this->findNext($point[0], $point[1]);
+        }
+
+        $this->bytes = $originalBytes;
+    }
+
+    /**
+     * @return int[]|null
+     */
+    private function findNext(int $x, int $y) : ?array
+    {
+        $i = $this->width * $y + $x;
+
+        while ($i < $this->size && 1 !== $this->bytes[$i]) {
+            ++$i;
+        }
+
+        if ($i < $this->size) {
+            return $this->pointOf($i);
+        }
+
+        return null;
+    }
+
+    private function findEdge(int $x, int $y) : Edge
+    {
+        $edge = new Edge($this->isSet($x, $y));
+        $startX = $x;
+        $startY = $y;
+        $dirX = 0;
+        $dirY = 1;
+
+        while (true) {
+            $edge->addPoint($x, $y);
+            $x += $dirX;
+            $y += $dirY;
+
+            if ($x === $startX && $y === $startY) {
+                break;
+            }
+
+            $left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2);
+            $right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2);
+
+            if ($right && ! $left) {
+                $tmp = $dirX;
+                $dirX = -$dirY;
+                $dirY = $tmp;
+            } elseif ($right) {
+                $tmp = $dirX;
+                $dirX = -$dirY;
+                $dirY = $tmp;
+            } elseif (! $left) {
+                $tmp = $dirX;
+                $dirX = $dirY;
+                $dirY = -$tmp;
+            }
+        }
+
+        return $edge;
+    }
+
+    private function xorEdge(Edge $path) : void
+    {
+        $points = $path->getPoints();
+        $y1 = $points[0][1];
+        $length = count($points);
+        $maxX = $path->getMaxX();
+
+        for ($i = 1; $i < $length; ++$i) {
+            $y = $points[$i][1];
+
+            if ($y === $y1) {
+                continue;
+            }
+
+            $x = $points[$i][0];
+            $minY = min($y1, $y);
+
+            for ($j = $x; $j < $maxX; ++$j) {
+                $this->flip($j, $minY);
+            }
+
+            $y1 = $y;
+        }
+    }
+
+    private function isSet(int $x, int $y) : bool
+    {
+        return (
+            $x >= 0
+            && $x < $this->width
+            && $y >= 0
+            && $y < $this->height
+        ) && 1 === $this->bytes[$this->width * $y + $x];
+    }
+
+    /**
+     * @return int[]
+     */
+    private function pointOf(int $i) : array
+    {
+        $y = intdiv($i, $this->width);
+        return [$i - $y * $this->width, $y];
+    }
+
+    private function flip(int $x, int $y) : void
+    {
+        $this->bytes[$this->width * $y + $x] = (
+            $this->isSet($x, $y) ? 0 : 1
+        );
+    }
+}

+ 18 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/ModuleInterface.php

@@ -0,0 +1,18 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Module;
+
+use BaconQrCode\Encoder\ByteMatrix;
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Interface describing how modules should be rendered.
+ *
+ * A module always receives a byte matrix (with values either being 1 or 0). It returns a path, where the origin
+ * coordinate (0, 0) equals the top left corner of the first matrix value.
+ */
+interface ModuleInterface
+{
+    public function createPath(ByteMatrix $matrix) : Path;
+}

+ 129 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/RoundnessModule.php

@@ -0,0 +1,129 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Module;
+
+use BaconQrCode\Encoder\ByteMatrix;
+use BaconQrCode\Exception\InvalidArgumentException;
+use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator;
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Rounds the corners of module groups.
+ */
+final class RoundnessModule implements ModuleInterface
+{
+    public const STRONG = 1;
+    public const MEDIUM = .5;
+    public const SOFT = .25;
+
+    /**
+     * @var float
+     */
+    private $intensity;
+
+    public function __construct(float $intensity)
+    {
+        if ($intensity <= 0 || $intensity > 1) {
+            throw new InvalidArgumentException('Intensity must between 0 (exclusive) and 1 (inclusive)');
+        }
+
+        $this->intensity = $intensity / 2;
+    }
+
+    public function createPath(ByteMatrix $matrix) : Path
+    {
+        $path = new Path();
+
+        foreach (new EdgeIterator($matrix) as $edge) {
+            $points = $edge->getSimplifiedPoints();
+            $length = count($points);
+
+            $currentPoint = $points[0];
+            $nextPoint = $points[1];
+            $horizontal = ($currentPoint[1] === $nextPoint[1]);
+
+            if ($horizontal) {
+                $right = $nextPoint[0] > $currentPoint[0];
+                $path = $path->move(
+                    $currentPoint[0] + ($right ? $this->intensity : -$this->intensity),
+                    $currentPoint[1]
+                );
+            } else {
+                $up = $nextPoint[0] < $currentPoint[0];
+                $path = $path->move(
+                    $currentPoint[0],
+                    $currentPoint[1] + ($up ? -$this->intensity : $this->intensity)
+                );
+            }
+
+            for ($i = 1; $i <= $length; ++$i) {
+                if ($i === $length) {
+                    $previousPoint = $points[$length - 1];
+                    $currentPoint = $points[0];
+                    $nextPoint = $points[1];
+                } else {
+                    $previousPoint = $points[(0 === $i ? $length : $i) - 1];
+                    $currentPoint = $points[$i];
+                    $nextPoint = $points[($length - 1 === $i ? -1 : $i) + 1];
+                }
+
+                $horizontal = ($previousPoint[1] === $currentPoint[1]);
+
+                if ($horizontal) {
+                    $right = $previousPoint[0] < $currentPoint[0];
+                    $up = $nextPoint[1] < $currentPoint[1];
+                    $sweep = ($up xor $right);
+
+                    if ($this->intensity < 0.5
+                        || ($right && $previousPoint[0] !== $currentPoint[0] - 1)
+                        || (! $right && $previousPoint[0] - 1 !== $currentPoint[0])
+                    ) {
+                        $path = $path->line(
+                            $currentPoint[0] + ($right ? -$this->intensity : $this->intensity),
+                            $currentPoint[1]
+                        );
+                    }
+
+                    $path = $path->ellipticArc(
+                        $this->intensity,
+                        $this->intensity,
+                        0,
+                        false,
+                        $sweep,
+                        $currentPoint[0],
+                        $currentPoint[1] + ($up ? -$this->intensity : $this->intensity)
+                    );
+                } else {
+                    $up = $previousPoint[1] > $currentPoint[1];
+                    $right = $nextPoint[0] > $currentPoint[0];
+                    $sweep = ! ($up xor $right);
+
+                    if ($this->intensity < 0.5
+                        || ($up && $previousPoint[1] !== $currentPoint[1] + 1)
+                        || (! $up && $previousPoint[0] + 1 !== $currentPoint[0])
+                    ) {
+                        $path = $path->line(
+                            $currentPoint[0],
+                            $currentPoint[1] + ($up ? $this->intensity : -$this->intensity)
+                        );
+                    }
+
+                    $path = $path->ellipticArc(
+                        $this->intensity,
+                        $this->intensity,
+                        0,
+                        false,
+                        $sweep,
+                        $currentPoint[0] + ($right ? $this->intensity : -$this->intensity),
+                        $currentPoint[1]
+                    );
+                }
+            }
+
+            $path = $path->close();
+        }
+
+        return $path;
+    }
+}

+ 47 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Module/SquareModule.php

@@ -0,0 +1,47 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Module;
+
+use BaconQrCode\Encoder\ByteMatrix;
+use BaconQrCode\Renderer\Module\EdgeIterator\EdgeIterator;
+use BaconQrCode\Renderer\Path\Path;
+
+/**
+ * Groups modules together to a single path.
+ */
+final class SquareModule implements ModuleInterface
+{
+    /**
+     * @var self|null
+     */
+    private static $instance;
+
+    private function __construct()
+    {
+    }
+
+    public static function instance() : self
+    {
+        return self::$instance ?: self::$instance = new self();
+    }
+
+    public function createPath(ByteMatrix $matrix) : Path
+    {
+        $path = new Path();
+
+        foreach (new EdgeIterator($matrix) as $edge) {
+            $points = $edge->getSimplifiedPoints();
+            $length = count($points);
+            $path = $path->move($points[0][0], $points[0][1]);
+
+            for ($i = 1; $i < $length; ++$i) {
+                $path = $path->line($points[$i][0], $points[$i][1]);
+            }
+
+            $path = $path->close();
+        }
+
+        return $path;
+    }
+}

+ 29 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Close.php

@@ -0,0 +1,29 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Path;
+
+final class Close implements OperationInterface
+{
+    /**
+     * @var self|null
+     */
+    private static $instance;
+
+    private function __construct()
+    {
+    }
+
+    public static function instance() : self
+    {
+        return self::$instance ?: self::$instance = new self();
+    }
+
+    /**
+     * @return self
+     */
+    public function translate(float $x, float $y) : OperationInterface
+    {
+        return $this;
+    }
+}

+ 92 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Curve.php

@@ -0,0 +1,92 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Path;
+
+final class Curve implements OperationInterface
+{
+    /**
+     * @var float
+     */
+    private $x1;
+
+    /**
+     * @var float
+     */
+    private $y1;
+
+    /**
+     * @var float
+     */
+    private $x2;
+
+    /**
+     * @var float
+     */
+    private $y2;
+
+    /**
+     * @var float
+     */
+    private $x3;
+
+    /**
+     * @var float
+     */
+    private $y3;
+
+    public function __construct(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3)
+    {
+        $this->x1 = $x1;
+        $this->y1 = $y1;
+        $this->x2 = $x2;
+        $this->y2 = $y2;
+        $this->x3 = $x3;
+        $this->y3 = $y3;
+    }
+
+    public function getX1() : float
+    {
+        return $this->x1;
+    }
+
+    public function getY1() : float
+    {
+        return $this->y1;
+    }
+
+    public function getX2() : float
+    {
+        return $this->x2;
+    }
+
+    public function getY2() : float
+    {
+        return $this->y2;
+    }
+
+    public function getX3() : float
+    {
+        return $this->x3;
+    }
+
+    public function getY3() : float
+    {
+        return $this->y3;
+    }
+
+    /**
+     * @return self
+     */
+    public function translate(float $x, float $y) : OperationInterface
+    {
+        return new self(
+            $this->x1 + $x,
+            $this->y1 + $y,
+            $this->x2 + $x,
+            $this->y2 + $y,
+            $this->x3 + $x,
+            $this->y3 + $y
+        );
+    }
+}

+ 278 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/EllipticArc.php

@@ -0,0 +1,278 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Path;
+
+final class EllipticArc implements OperationInterface
+{
+    private const ZERO_TOLERANCE = 1e-05;
+
+    /**
+     * @var float
+     */
+    private $xRadius;
+
+    /**
+     * @var float
+     */
+    private $yRadius;
+
+    /**
+     * @var float
+     */
+    private $xAxisAngle;
+
+    /**
+     * @var bool
+     */
+    private $largeArc;
+
+    /**
+     * @var bool
+     */
+    private $sweep;
+
+    /**
+     * @var float
+     */
+    private $x;
+
+    /**
+     * @var float
+     */
+    private $y;
+
+    public function __construct(
+        float $xRadius,
+        float $yRadius,
+        float $xAxisAngle,
+        bool $largeArc,
+        bool $sweep,
+        float $x,
+        float $y
+    ) {
+        $this->xRadius = abs($xRadius);
+        $this->yRadius = abs($yRadius);
+        $this->xAxisAngle = $xAxisAngle % 360;
+        $this->largeArc = $largeArc;
+        $this->sweep = $sweep;
+        $this->x = $x;
+        $this->y = $y;
+    }
+
+    public function getXRadius() : float
+    {
+        return $this->xRadius;
+    }
+
+    public function getYRadius() : float
+    {
+        return $this->yRadius;
+    }
+
+    public function getXAxisAngle() : float
+    {
+        return $this->xAxisAngle;
+    }
+
+    public function isLargeArc() : bool
+    {
+        return $this->largeArc;
+    }
+
+    public function isSweep() : bool
+    {
+        return $this->sweep;
+    }
+
+    public function getX() : float
+    {
+        return $this->x;
+    }
+
+    public function getY() : float
+    {
+        return $this->y;
+    }
+
+    /**
+     * @return self
+     */
+    public function translate(float $x, float $y) : OperationInterface
+    {
+        return new self(
+            $this->xRadius,
+            $this->yRadius,
+            $this->xAxisAngle,
+            $this->largeArc,
+            $this->sweep,
+            $this->x + $x,
+            $this->y + $y
+        );
+    }
+
+    /**
+     * Converts the elliptic arc to multiple curves.
+     *
+     * Since not all image back ends support elliptic arcs, this method allows to convert the arc into multiple curves
+     * resembling the same result.
+     *
+     * @see https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/
+     * @return array<Curve|Line>
+     */
+    public function toCurves(float $fromX, float $fromY) : array
+    {
+        if (sqrt(($fromX - $this->x) ** 2 + ($fromY - $this->y) ** 2) < self::ZERO_TOLERANCE) {
+            return [];
+        }
+
+        if ($this->xRadius < self::ZERO_TOLERANCE || $this->yRadius < self::ZERO_TOLERANCE) {
+            return [new Line($this->x, $this->y)];
+        }
+
+        return $this->createCurves($fromX, $fromY);
+    }
+
+    /**
+     * @return Curve[]
+     */
+    private function createCurves(float $fromX, float $fromY) : array
+    {
+        $xAngle = deg2rad($this->xAxisAngle);
+        list($centerX, $centerY, $radiusX, $radiusY, $startAngle, $deltaAngle) =
+            $this->calculateCenterPointParameters($fromX, $fromY, $xAngle);
+
+        $s = $startAngle;
+        $e = $s + $deltaAngle;
+        $sign = ($e < $s) ? -1 : 1;
+        $remain = abs($e - $s);
+        $p1 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s);
+        $curves = [];
+
+        while ($remain > self::ZERO_TOLERANCE) {
+            $step = min($remain, pi() / 2);
+            $signStep = $step * $sign;
+            $p2 = self::point($centerX, $centerY, $radiusX, $radiusY, $xAngle, $s + $signStep);
+
+            $alphaT = tan($signStep / 2);
+            $alpha = sin($signStep) * (sqrt(4 + 3 * $alphaT ** 2) - 1) / 3;
+            $d1 = self::derivative($radiusX, $radiusY, $xAngle, $s);
+            $d2 = self::derivative($radiusX, $radiusY, $xAngle, $s + $signStep);
+
+            $curves[] = new Curve(
+                $p1[0] + $alpha * $d1[0],
+                $p1[1] + $alpha * $d1[1],
+                $p2[0] - $alpha * $d2[0],
+                $p2[1] - $alpha * $d2[1],
+                $p2[0],
+                $p2[1]
+            );
+
+            $s += $signStep;
+            $remain -= $step;
+            $p1 = $p2;
+        }
+
+        return $curves;
+    }
+
+    /**
+     * @return float[]
+     */
+    private function calculateCenterPointParameters(float $fromX, float $fromY, float $xAngle)
+    {
+        $rX = $this->xRadius;
+        $rY = $this->yRadius;
+
+        // F.6.5.1
+        $dx2 = ($fromX - $this->x) / 2;
+        $dy2 = ($fromY - $this->y) / 2;
+        $x1p = cos($xAngle) * $dx2 + sin($xAngle) * $dy2;
+        $y1p = -sin($xAngle) * $dx2 + cos($xAngle) * $dy2;
+
+        // F.6.5.2
+        $rxs = $rX ** 2;
+        $rys = $rY ** 2;
+        $x1ps = $x1p ** 2;
+        $y1ps = $y1p ** 2;
+        $cr = $x1ps / $rxs + $y1ps / $rys;
+
+        if ($cr > 1) {
+            $s = sqrt($cr);
+            $rX *= $s;
+            $rY *= $s;
+            $rxs = $rX ** 2;
+            $rys = $rY ** 2;
+        }
+
+        $dq = ($rxs * $y1ps + $rys * $x1ps);
+        $pq = ($rxs * $rys - $dq) / $dq;
+        $q = sqrt(max(0, $pq));
+
+        if ($this->largeArc === $this->sweep) {
+            $q = -$q;
+        }
+
+        $cxp = $q * $rX * $y1p / $rY;
+        $cyp = -$q * $rY * $x1p / $rX;
+
+        // F.6.5.3
+        $cx = cos($xAngle) * $cxp - sin($xAngle) * $cyp + ($fromX + $this->x) / 2;
+        $cy = sin($xAngle) * $cxp + cos($xAngle) * $cyp + ($fromY + $this->y) / 2;
+
+        // F.6.5.5
+        $theta = self::angle(1, 0, ($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY);
+
+        // F.6.5.6
+        $delta = self::angle(($x1p - $cxp) / $rX, ($y1p - $cyp) / $rY, (-$x1p - $cxp) / $rX, (-$y1p - $cyp) / $rY);
+        $delta = fmod($delta, pi() * 2);
+
+        if (! $this->sweep) {
+            $delta -= 2 * pi();
+        }
+
+        return [$cx, $cy, $rX, $rY, $theta, $delta];
+    }
+
+    private static function angle(float $ux, float $uy, float $vx, float $vy) : float
+    {
+        // F.6.5.4
+        $dot = $ux * $vx + $uy * $vy;
+        $length = sqrt($ux ** 2 + $uy ** 2) * sqrt($vx ** 2 + $vy ** 2);
+        $angle = acos(min(1, max(-1, $dot / $length)));
+
+        if (($ux * $vy - $uy * $vx) < 0) {
+            return -$angle;
+        }
+
+        return $angle;
+    }
+
+    /**
+     * @return float[]
+     */
+    private static function point(
+        float $centerX,
+        float $centerY,
+        float $radiusX,
+        float $radiusY,
+        float $xAngle,
+        float $angle
+    ) : array {
+        return [
+            $centerX + $radiusX * cos($xAngle) * cos($angle) - $radiusY * sin($xAngle) * sin($angle),
+            $centerY + $radiusX * sin($xAngle) * cos($angle) + $radiusY * cos($xAngle) * sin($angle),
+        ];
+    }
+
+    /**
+     * @return float[]
+     */
+    private static function derivative(float $radiusX, float $radiusY, float $xAngle, float $angle) : array
+    {
+        return [
+            -$radiusX * cos($xAngle) * sin($angle) - $radiusY * sin($xAngle) * cos($angle),
+            -$radiusX * sin($xAngle) * sin($angle) + $radiusY * cos($xAngle) * cos($angle),
+        ];
+    }
+}

+ 41 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Line.php

@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Path;
+
+final class Line implements OperationInterface
+{
+    /**
+     * @var float
+     */
+    private $x;
+
+    /**
+     * @var float
+     */
+    private $y;
+
+    public function __construct(float $x, float $y)
+    {
+        $this->x = $x;
+        $this->y = $y;
+    }
+
+    public function getX() : float
+    {
+        return $this->x;
+    }
+
+    public function getY() : float
+    {
+        return $this->y;
+    }
+
+    /**
+     * @return self
+     */
+    public function translate(float $x, float $y) : OperationInterface
+    {
+        return new self($this->x + $x, $this->y + $y);
+    }
+}

+ 41 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Move.php

@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Path;
+
+final class Move implements OperationInterface
+{
+    /**
+     * @var float
+     */
+    private $x;
+
+    /**
+     * @var float
+     */
+    private $y;
+
+    public function __construct(float $x, float $y)
+    {
+        $this->x = $x;
+        $this->y = $y;
+    }
+
+    public function getX() : float
+    {
+        return $this->x;
+    }
+
+    public function getY() : float
+    {
+        return $this->y;
+    }
+
+    /**
+     * @return self
+     */
+    public function translate(float $x, float $y) : OperationInterface
+    {
+        return new self($this->x + $x, $this->y + $y);
+    }
+}

+ 12 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/OperationInterface.php

@@ -0,0 +1,12 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Path;
+
+interface OperationInterface
+{
+    /**
+     * Translates the operation's coordinates.
+     */
+    public function translate(float $x, float $y) : self;
+}

+ 106 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/Path/Path.php

@@ -0,0 +1,106 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\Path;
+
+use IteratorAggregate;
+use Traversable;
+
+/**
+ * Internal Representation of a vector path.
+ */
+final class Path implements IteratorAggregate
+{
+    /**
+     * @var OperationInterface[]
+     */
+    private $operations = [];
+
+    /**
+     * Moves the drawing operation to a certain position.
+     */
+    public function move(float $x, float $y) : self
+    {
+        $path = clone $this;
+        $path->operations[] = new Move($x, $y);
+        return $path;
+    }
+
+    /**
+     * Draws a line from the current position to another position.
+     */
+    public function line(float $x, float $y) : self
+    {
+        $path = clone $this;
+        $path->operations[] = new Line($x, $y);
+        return $path;
+    }
+
+    /**
+     * Draws an elliptic arc from the current position to another position.
+     */
+    public function ellipticArc(
+        float $xRadius,
+        float $yRadius,
+        float $xAxisRotation,
+        bool $largeArc,
+        bool $sweep,
+        float $x,
+        float $y
+    ) : self {
+        $path = clone $this;
+        $path->operations[] = new EllipticArc($xRadius, $yRadius, $xAxisRotation, $largeArc, $sweep, $x, $y);
+        return $path;
+    }
+
+    /**
+     * Draws a curve from the current position to another position.
+     */
+    public function curve(float $x1, float $y1, float $x2, float $y2, float $x3, float $y3) : self
+    {
+        $path = clone $this;
+        $path->operations[] = new Curve($x1, $y1, $x2, $y2, $x3, $y3);
+        return $path;
+    }
+
+    /**
+     * Closes a sub-path.
+     */
+    public function close() : self
+    {
+        $path = clone $this;
+        $path->operations[] = Close::instance();
+        return $path;
+    }
+
+    /**
+     * Appends another path to this one.
+     */
+    public function append(self $other) : self
+    {
+        $path = clone $this;
+        $path->operations = array_merge($this->operations, $other->operations);
+        return $path;
+    }
+
+    public function translate(float $x, float $y) : self
+    {
+        $path = new self();
+
+        foreach ($this->operations as $operation) {
+            $path->operations[] = $operation->translate($x, $y);
+        }
+
+        return $path;
+    }
+
+    /**
+     * @return OperationInterface[]|Traversable
+     */
+    public function getIterator() : Traversable
+    {
+        foreach ($this->operations as $operation) {
+            yield $operation;
+        }
+    }
+}

+ 86 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/PlainTextRenderer.php

@@ -0,0 +1,86 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer;
+
+use BaconQrCode\Encoder\QrCode;
+use BaconQrCode\Exception\InvalidArgumentException;
+
+final class PlainTextRenderer implements RendererInterface
+{
+    /**
+     * UTF-8 full block (U+2588)
+     */
+    private const FULL_BLOCK = "\xe2\x96\x88";
+
+    /**
+     * UTF-8 upper half block (U+2580)
+     */
+    private const UPPER_HALF_BLOCK = "\xe2\x96\x80";
+
+    /**
+     * UTF-8 lower half block (U+2584)
+     */
+    private const LOWER_HALF_BLOCK = "\xe2\x96\x84";
+
+    /**
+     * UTF-8 no-break space (U+00A0)
+     */
+    private const EMPTY_BLOCK = "\xc2\xa0";
+
+    /**
+     * @var int
+     */
+    private $margin;
+
+    public function __construct(int $margin = 2)
+    {
+        $this->margin = $margin;
+    }
+
+    /**
+     * @throws InvalidArgumentException if matrix width doesn't match height
+     */
+    public function render(QrCode $qrCode) : string
+    {
+        $matrix = $qrCode->getMatrix();
+        $matrixSize = $matrix->getWidth();
+
+        if ($matrixSize !== $matrix->getHeight()) {
+            throw new InvalidArgumentException('Matrix must have the same width and height');
+        }
+
+        $rows = $matrix->getArray()->toArray();
+
+        if (0 !== $matrixSize % 2) {
+            $rows[] = array_fill(0, $matrixSize, 0);
+        }
+
+        $horizontalMargin = str_repeat(self::EMPTY_BLOCK, $this->margin);
+        $result = str_repeat("\n", (int) ceil($this->margin / 2));
+
+        for ($i = 0; $i < $matrixSize; $i += 2) {
+            $result .= $horizontalMargin;
+
+            $upperRow = $rows[$i];
+            $lowerRow = $rows[$i + 1];
+
+            for ($j = 0; $j < $matrixSize; ++$j) {
+                $upperBit = $upperRow[$j];
+                $lowerBit = $lowerRow[$j];
+
+                if ($upperBit) {
+                    $result .= $lowerBit ? self::FULL_BLOCK : self::UPPER_HALF_BLOCK;
+                } else {
+                    $result .= $lowerBit ? self::LOWER_HALF_BLOCK : self::EMPTY_BLOCK;
+                }
+            }
+
+            $result .= $horizontalMargin . "\n";
+        }
+
+        $result .= str_repeat("\n", (int) ceil($this->margin / 2));
+
+        return $result;
+    }
+}

+ 11 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererInterface.php

@@ -0,0 +1,11 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer;
+
+use BaconQrCode\Encoder\QrCode;
+
+interface RendererInterface
+{
+    public function render(QrCode $qrCode) : string;
+}

+ 74 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/EyeFill.php

@@ -0,0 +1,74 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\RendererStyle;
+
+use BaconQrCode\Exception\RuntimeException;
+use BaconQrCode\Renderer\Color\ColorInterface;
+
+final class EyeFill
+{
+    /**
+     * @var ColorInterface|null
+     */
+    private $externalColor;
+
+    /**
+     * @var ColorInterface|null
+     */
+    private $internalColor;
+
+    /**
+     * @var self|null
+     */
+    private static $inherit;
+
+    public function __construct(?ColorInterface $externalColor, ?ColorInterface $internalColor)
+    {
+        $this->externalColor = $externalColor;
+        $this->internalColor = $internalColor;
+    }
+
+    public static function uniform(ColorInterface $color) : self
+    {
+        return new self($color, $color);
+    }
+
+    public static function inherit() : self
+    {
+        return self::$inherit ?: self::$inherit = new self(null, null);
+    }
+
+    public function inheritsBothColors() : bool
+    {
+        return null === $this->externalColor && null === $this->internalColor;
+    }
+
+    public function inheritsExternalColor() : bool
+    {
+        return null === $this->externalColor;
+    }
+
+    public function inheritsInternalColor() : bool
+    {
+        return null === $this->internalColor;
+    }
+
+    public function getExternalColor() : ColorInterface
+    {
+        if (null === $this->externalColor) {
+            throw new RuntimeException('External eye color inherits foreground color');
+        }
+
+        return $this->externalColor;
+    }
+
+    public function getInternalColor() : ColorInterface
+    {
+        if (null === $this->internalColor) {
+            throw new RuntimeException('Internal eye color inherits foreground color');
+        }
+
+        return $this->internalColor;
+    }
+}

+ 168 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/Fill.php

@@ -0,0 +1,168 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\RendererStyle;
+
+use BaconQrCode\Exception\RuntimeException;
+use BaconQrCode\Renderer\Color\ColorInterface;
+use BaconQrCode\Renderer\Color\Gray;
+
+final class Fill
+{
+    /**
+     * @var ColorInterface
+     */
+    private $backgroundColor;
+
+    /**
+     * @var ColorInterface|null
+     */
+    private $foregroundColor;
+
+    /**
+     * @var Gradient|null
+     */
+    private $foregroundGradient;
+
+    /**
+     * @var EyeFill
+     */
+    private $topLeftEyeFill;
+
+    /**
+     * @var EyeFill
+     */
+    private $topRightEyeFill;
+
+    /**
+     * @var EyeFill
+     */
+    private $bottomLeftEyeFill;
+
+    /**
+     * @var self|null
+     */
+    private static $default;
+
+    private function __construct(
+        ColorInterface $backgroundColor,
+        ?ColorInterface $foregroundColor,
+        ?Gradient $foregroundGradient,
+        EyeFill $topLeftEyeFill,
+        EyeFill $topRightEyeFill,
+        EyeFill $bottomLeftEyeFill
+    ) {
+        $this->backgroundColor = $backgroundColor;
+        $this->foregroundColor = $foregroundColor;
+        $this->foregroundGradient = $foregroundGradient;
+        $this->topLeftEyeFill = $topLeftEyeFill;
+        $this->topRightEyeFill = $topRightEyeFill;
+        $this->bottomLeftEyeFill = $bottomLeftEyeFill;
+    }
+
+    public static function default() : self
+    {
+        return self::$default ?: self::$default = self::uniformColor(new Gray(100), new Gray(0));
+    }
+
+    public static function withForegroundColor(
+        ColorInterface $backgroundColor,
+        ColorInterface $foregroundColor,
+        EyeFill $topLeftEyeFill,
+        EyeFill $topRightEyeFill,
+        EyeFill $bottomLeftEyeFill
+    ) : self {
+        return new self(
+            $backgroundColor,
+            $foregroundColor,
+            null,
+            $topLeftEyeFill,
+            $topRightEyeFill,
+            $bottomLeftEyeFill
+        );
+    }
+
+    public static function withForegroundGradient(
+        ColorInterface $backgroundColor,
+        Gradient $foregroundGradient,
+        EyeFill $topLeftEyeFill,
+        EyeFill $topRightEyeFill,
+        EyeFill $bottomLeftEyeFill
+    ) : self {
+        return new self(
+            $backgroundColor,
+            null,
+            $foregroundGradient,
+            $topLeftEyeFill,
+            $topRightEyeFill,
+            $bottomLeftEyeFill
+        );
+    }
+
+    public static function uniformColor(ColorInterface $backgroundColor, ColorInterface $foregroundColor) : self
+    {
+        return new self(
+            $backgroundColor,
+            $foregroundColor,
+            null,
+            EyeFill::inherit(),
+            EyeFill::inherit(),
+            EyeFill::inherit()
+        );
+    }
+
+    public static function uniformGradient(ColorInterface $backgroundColor, Gradient $foregroundGradient) : self
+    {
+        return new self(
+            $backgroundColor,
+            null,
+            $foregroundGradient,
+            EyeFill::inherit(),
+            EyeFill::inherit(),
+            EyeFill::inherit()
+        );
+    }
+
+    public function hasGradientFill() : bool
+    {
+        return null !== $this->foregroundGradient;
+    }
+
+    public function getBackgroundColor() : ColorInterface
+    {
+        return $this->backgroundColor;
+    }
+
+    public function getForegroundColor() : ColorInterface
+    {
+        if (null === $this->foregroundColor) {
+            throw new RuntimeException('Fill uses a gradient, thus no foreground color is available');
+        }
+
+        return $this->foregroundColor;
+    }
+
+    public function getForegroundGradient() : Gradient
+    {
+        if (null === $this->foregroundGradient) {
+            throw new RuntimeException('Fill uses a single color, thus no foreground gradient is available');
+        }
+
+        return $this->foregroundGradient;
+    }
+
+    public function getTopLeftEyeFill() : EyeFill
+    {
+        return $this->topLeftEyeFill;
+    }
+
+    public function getTopRightEyeFill() : EyeFill
+    {
+        return $this->topRightEyeFill;
+    }
+
+    public function getBottomLeftEyeFill() : EyeFill
+    {
+        return $this->bottomLeftEyeFill;
+    }
+}

+ 46 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/Gradient.php

@@ -0,0 +1,46 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\RendererStyle;
+
+use BaconQrCode\Renderer\Color\ColorInterface;
+
+final class Gradient
+{
+    /**
+     * @var ColorInterface
+     */
+    private $startColor;
+
+    /**
+     * @var ColorInterface
+     */
+    private $endColor;
+
+    /**
+     * @var GradientType
+     */
+    private $type;
+
+    public function __construct(ColorInterface $startColor, ColorInterface $endColor, GradientType $type)
+    {
+        $this->startColor = $startColor;
+        $this->endColor = $endColor;
+        $this->type = $type;
+    }
+
+    public function getStartColor() : ColorInterface
+    {
+        return $this->startColor;
+    }
+
+    public function getEndColor() : ColorInterface
+    {
+        return $this->endColor;
+    }
+
+    public function getType() : GradientType
+    {
+        return $this->type;
+    }
+}

+ 22 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/GradientType.php

@@ -0,0 +1,22 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\RendererStyle;
+
+use DASPRiD\Enum\AbstractEnum;
+
+/**
+ * @method static self VERTICAL()
+ * @method static self HORIZONTAL()
+ * @method static self DIAGONAL()
+ * @method static self INVERSE_DIAGONAL()
+ * @method static self RADIAL()
+ */
+final class GradientType extends AbstractEnum
+{
+    protected const VERTICAL = null;
+    protected const HORIZONTAL = null;
+    protected const DIAGONAL = null;
+    protected const INVERSE_DIAGONAL = null;
+    protected const RADIAL = null;
+}

+ 90 - 0
htdocs/includes/bacon/bacon-qr-code/src/Renderer/RendererStyle/RendererStyle.php

@@ -0,0 +1,90 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode\Renderer\RendererStyle;
+
+use BaconQrCode\Renderer\Eye\EyeInterface;
+use BaconQrCode\Renderer\Eye\ModuleEye;
+use BaconQrCode\Renderer\Module\ModuleInterface;
+use BaconQrCode\Renderer\Module\SquareModule;
+
+final class RendererStyle
+{
+    /**
+     * @var int
+     */
+    private $size;
+
+    /**
+     * @var int
+     */
+    private $margin;
+
+    /**
+     * @var ModuleInterface
+     */
+    private $module;
+
+    /**
+     * @var EyeInterface|null
+     */
+    private $eye;
+
+    /**
+     * @var Fill
+     */
+    private $fill;
+
+    public function __construct(
+        int $size,
+        int $margin = 4,
+        ?ModuleInterface $module = null,
+        ?EyeInterface $eye = null,
+        ?Fill $fill = null
+    ) {
+        $this->margin = $margin;
+        $this->size = $size;
+        $this->module = $module ?: SquareModule::instance();
+        $this->eye = $eye ?: new ModuleEye($this->module);
+        $this->fill = $fill ?: Fill::default();
+    }
+
+    public function withSize(int $size) : self
+    {
+        $style = clone $this;
+        $style->size = $size;
+        return $style;
+    }
+
+    public function withMargin(int $margin) : self
+    {
+        $style = clone $this;
+        $style->margin = $margin;
+        return $style;
+    }
+
+    public function getSize() : int
+    {
+        return $this->size;
+    }
+
+    public function getMargin() : int
+    {
+        return $this->margin;
+    }
+
+    public function getModule() : ModuleInterface
+    {
+        return $this->module;
+    }
+
+    public function getEye() : EyeInterface
+    {
+        return $this->eye;
+    }
+
+    public function getFill() : Fill
+    {
+        return $this->fill;
+    }
+}

+ 71 - 0
htdocs/includes/bacon/bacon-qr-code/src/Writer.php

@@ -0,0 +1,71 @@
+<?php
+declare(strict_types = 1);
+
+namespace BaconQrCode;
+
+use BaconQrCode\Common\ErrorCorrectionLevel;
+use BaconQrCode\Common\Version;
+use BaconQrCode\Encoder\Encoder;
+use BaconQrCode\Exception\InvalidArgumentException;
+use BaconQrCode\Renderer\RendererInterface;
+
+/**
+ * QR code writer.
+ */
+final class Writer
+{
+    /**
+     * Renderer instance.
+     *
+     * @var RendererInterface
+     */
+    private $renderer;
+
+    /**
+     * Creates a new writer with a specific renderer.
+     */
+    public function __construct(RendererInterface $renderer)
+    {
+        $this->renderer = $renderer;
+    }
+
+    /**
+     * Writes QR code and returns it as string.
+     *
+     * Content is a string which *should* be encoded in UTF-8, in case there are
+     * non ASCII-characters present.
+     *
+     * @throws InvalidArgumentException if the content is empty
+     */
+    public function writeString(
+        string $content,
+        string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
+        ?ErrorCorrectionLevel $ecLevel = null,
+        ?Version $forcedVersion = null
+    ) : string {
+        if (strlen($content) === 0) {
+            throw new InvalidArgumentException('Found empty contents');
+        }
+
+        if (null === $ecLevel) {
+            $ecLevel = ErrorCorrectionLevel::L();
+        }
+
+        return $this->renderer->render(Encoder::encode($content, $ecLevel, $encoding, $forcedVersion));
+    }
+
+    /**
+     * Writes QR code to a file.
+     *
+     * @see Writer::writeString()
+     */
+    public function writeFile(
+        string $content,
+        string $filename,
+        string $encoding = Encoder::DEFAULT_BYTE_MODE_ECODING,
+        ?ErrorCorrectionLevel $ecLevel = null,
+        ?Version $forcedVersion = null
+    ) : void {
+        file_put_contents($filename, $this->writeString($content, $encoding, $ecLevel, $forcedVersion));
+    }
+}

+ 47 - 0
htdocs/includes/dasprid/enum/.github/workflows/tests.yml

@@ -0,0 +1,47 @@
+name: Tests
+
+on: [push, pull_request]
+
+jobs:
+  php-tests:
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: true
+      matrix:
+        php: [8.2, 8.1, 8.0, 7.4, 7.3, 7.2, 7.1]
+        dependency-version: [prefer-stable]
+        os: [ubuntu-latest, windows-latest]
+
+    name: ${{ matrix.os }} - PHP${{ matrix.php }} - ${{ matrix.dependency-version }}
+
+    steps:
+      - name: Set git to use LF
+        run: |
+          git config --global core.autocrlf false
+          git config --global core.eol lf
+
+      - name: Checkout code
+        uses: actions/checkout@v3
+
+      - name: Cache dependencies
+        uses: actions/cache@v3
+        with:
+          path: ~/.composer/cache/files
+          key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
+
+      - name: Setup PHP
+        uses: shivammathur/setup-php@v2
+        with:
+          php-version: ${{ matrix.php }}
+          coverage: none
+
+      - name: Install dependencies
+        run: |
+          composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
+
+      - name: Execute tests
+        run: vendor/bin/phpunit
+
+      - name: Code Sniffer
+        run: vendor/bin/phpcs
+

+ 22 - 0
htdocs/includes/dasprid/enum/LICENSE

@@ -0,0 +1,22 @@
+Copyright (c) 2017, Ben Scholzen 'DASPRiD'
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 164 - 0
htdocs/includes/dasprid/enum/README.md

@@ -0,0 +1,164 @@
+# PHP 7.1 enums
+
+[![Build Status](https://github.com/DASPRiD/Enum/actions/workflows/tests.yml/badge.svg)](https://github.com/DASPRiD/Enum/actions?query=workflow%3Atests)
+[![Coverage Status](https://coveralls.io/repos/github/DASPRiD/Enum/badge.svg?branch=master)](https://coveralls.io/github/DASPRiD/Enum?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/dasprid/enum/v/stable)](https://packagist.org/packages/dasprid/enum)
+[![Total Downloads](https://poser.pugx.org/dasprid/enum/downloads)](https://packagist.org/packages/dasprid/enum)
+[![License](https://poser.pugx.org/dasprid/enum/license)](https://packagist.org/packages/dasprid/enum)
+
+It is a well known fact that PHP is missing a basic enum type, ignoring the rather incomplete `SplEnum` implementation
+which is only available as a PECL extension. There are also quite a few other userland enum implementations around,
+but all of them have one or another compromise. This library tries to close that gap as far as PHP allows it to.
+
+## Usage
+
+### Basics
+
+At its core, there is the `DASPRiD\Enum\AbstractEnum` class, which by default will work with constants like any other
+enum implementation you might know. The first clear difference is that you should define all the constants as protected
+(so nobody outside your class can read them but the `AbstractEnum` can still do so). The other even mightier difference
+is that, for simple enums, the value of the constant doesn't matter at all. Let's have a look at a simple example:
+
+```php
+use DASPRiD\Enum\AbstractEnum;
+
+/**
+ * @method static self MONDAY()
+ * @method static self TUESDAY()
+ * @method static self WEDNESDAY()
+ * @method static self THURSDAY()
+ * @method static self FRIDAY()
+ * @method static self SATURDAY()
+ * @method static self SUNDAY()
+ */
+final class WeekDay extends AbstractEnum
+{
+    protected const MONDAY = null;
+    protected const TUESDAY = null;
+    protected const WEDNESDAY = null;
+    protected const THURSDAY = null;
+    protected const FRIDAY = null;
+    protected const SATURDAY = null;
+    protected const SUNDAY = null;
+}
+```
+
+If you need to provide constants for either internal use or public use, you can mark them as either private or public,
+in which case they will be ignored by the enum, which only considers protected constants as valid values. As you can
+see, we specifically defined the generated magic methods in a class level doc block, so anyone using this class will
+automatically have proper auto-completion in their IDE. Now since you have defined the enum, you can simply use it like
+that:
+
+```php
+function tellItLikeItIs(WeekDay $weekDay)
+{
+    switch ($weekDay) {
+        case WeekDay::MONDAY():
+            echo 'Mondays are bad.';
+            break;
+
+        case WeekDay::FRIDAY():
+            echo 'Fridays are better.';
+            break;
+
+        case WeekDay::SATURDAY():
+        case WeekDay::SUNDAY():
+            echo 'Weekends are best.';
+            break;
+
+        default:
+            echo 'Midweek days are so-so.';
+    }
+}
+
+tellItLikeItIs(WeekDay::MONDAY());
+tellItLikeItIs(WeekDay::WEDNESDAY());
+tellItLikeItIs(WeekDay::FRIDAY());
+tellItLikeItIs(WeekDay::SATURDAY());
+tellItLikeItIs(WeekDay::SUNDAY());
+```
+
+### More complex example
+
+Of course, all enums are singletons, which are not cloneable or serializable. Thus you can be sure that there is always
+just one instance of the same type. Of course, the values of constants are not completely useless, let's have a look at
+a more complex example:
+
+```php
+use DASPRiD\Enum\AbstractEnum;
+
+/**
+ * @method static self MERCURY()
+ * @method static self VENUS()
+ * @method static self EARTH()
+ * @method static self MARS()
+ * @method static self JUPITER()
+ * @method static self SATURN()
+ * @method static self URANUS()
+ * @method static self NEPTUNE()
+ */
+final class Planet extends AbstractEnum
+{
+    protected const MERCURY = [3.303e+23, 2.4397e6];
+    protected const VENUS = [4.869e+24, 6.0518e6];
+    protected const EARTH = [5.976e+24, 6.37814e6];
+    protected const MARS = [6.421e+23, 3.3972e6];
+    protected const JUPITER = [1.9e+27, 7.1492e7];
+    protected const SATURN = [5.688e+26, 6.0268e7];
+    protected const URANUS = [8.686e+25, 2.5559e7];
+    protected const NEPTUNE = [1.024e+26, 2.4746e7];
+
+    /**
+     * Universal gravitational constant.
+     *
+     * @var float
+     */
+    private const G = 6.67300E-11;
+
+    /**
+     * Mass in kilograms.
+     *
+     * @var float
+     */
+    private $mass;
+
+    /**
+     * Radius in meters.
+     *
+     * @var float
+     */
+    private $radius;
+
+    protected function __construct(float $mass, float $radius)
+    {
+        $this->mass = $mass;
+        $this->radius = $radius;
+    }
+
+    public function mass() : float
+    {
+        return $this->mass;
+    }
+
+    public function radius() : float
+    {
+        return $this->radius;
+    }
+
+    public function surfaceGravity() : float
+    {
+        return self::G * $this->mass / ($this->radius * $this->radius);
+    }
+
+    public function surfaceWeight(float $otherMass) : float
+    {
+        return $otherMass * $this->surfaceGravity();
+    }
+}
+
+$myMass = 80;
+
+foreach (Planet::values() as $planet) {
+    printf("Your weight on %s is %f\n", $planet, $planet->surfaceWeight($myMass));
+}
+```

+ 34 - 0
htdocs/includes/dasprid/enum/composer.json.disabled

@@ -0,0 +1,34 @@
+{
+    "name": "dasprid/enum",
+    "description": "PHP 7.1 enum implementation",
+    "license": "BSD-2-Clause",
+    "authors": [
+        {
+            "name": "Ben Scholzen 'DASPRiD'",
+            "email": "mail@dasprids.de",
+            "homepage": "https://dasprids.de/",
+            "role": "Developer"
+        }
+    ],
+    "keywords": [
+        "enum",
+        "map"
+    ],
+    "require": {
+        "php": ">=7.1 <9.0"
+    },
+    "require-dev": {
+        "phpunit/phpunit": "^7 | ^8 | ^9",
+        "squizlabs/php_codesniffer": "*"
+    },
+    "autoload": {
+        "psr-4": {
+            "DASPRiD\\Enum\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "DASPRiD\\EnumTest\\": "test/"
+        }
+    }
+}

+ 241 - 0
htdocs/includes/dasprid/enum/src/AbstractEnum.php

@@ -0,0 +1,241 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum;
+
+use DASPRiD\Enum\Exception\CloneNotSupportedException;
+use DASPRiD\Enum\Exception\IllegalArgumentException;
+use DASPRiD\Enum\Exception\MismatchException;
+use DASPRiD\Enum\Exception\SerializeNotSupportedException;
+use DASPRiD\Enum\Exception\UnserializeNotSupportedException;
+use ReflectionClass;
+
+abstract class AbstractEnum
+{
+    /**
+     * @var string
+     */
+    private $name;
+
+    /**
+     * @var int
+     */
+    private $ordinal;
+
+    /**
+     * @var array<string, array<string, static>>
+     */
+    private static $values = [];
+
+    /**
+     * @var array<string, bool>
+     */
+    private static $allValuesLoaded = [];
+
+    /**
+     * @var array<string, array>
+     */
+    private static $constants = [];
+
+    /**
+     * The constructor is private by default to avoid arbitrary enum creation.
+     *
+     * When creating your own constructor for a parameterized enum, make sure to declare it as protected, so that
+     * the static methods are able to construct it. Avoid making it public, as that would allow creation of
+     * non-singleton enum instances.
+     */
+    private function __construct()
+    {
+    }
+
+    /**
+     * Magic getter which forwards all calls to {@see self::valueOf()}.
+     *
+     * @return static
+     */
+    final public static function __callStatic(string $name, array $arguments) : self
+    {
+        return static::valueOf($name);
+    }
+
+    /**
+     * Returns an enum with the specified name.
+     *
+     * The name must match exactly an identifier used to declare an enum in this type (extraneous whitespace characters
+     * are not permitted).
+     *
+     * @return static
+     * @throws IllegalArgumentException if the enum has no constant with the specified name
+     */
+    final public static function valueOf(string $name) : self
+    {
+        if (isset(self::$values[static::class][$name])) {
+            return self::$values[static::class][$name];
+        }
+
+        $constants = self::constants();
+
+        if (array_key_exists($name, $constants)) {
+            return self::createValue($name, $constants[$name][0], $constants[$name][1]);
+        }
+
+        throw new IllegalArgumentException(sprintf('No enum constant %s::%s', static::class, $name));
+    }
+
+    /**
+     * @return static
+     */
+    private static function createValue(string $name, int $ordinal, array $arguments) : self
+    {
+        $instance = new static(...$arguments);
+        $instance->name = $name;
+        $instance->ordinal = $ordinal;
+        self::$values[static::class][$name] = $instance;
+        return $instance;
+    }
+
+    /**
+     * Obtains all possible types defined by this enum.
+     *
+     * @return static[]
+     */
+    final public static function values() : array
+    {
+        if (isset(self::$allValuesLoaded[static::class])) {
+            return self::$values[static::class];
+        }
+
+        if (! isset(self::$values[static::class])) {
+            self::$values[static::class] = [];
+        }
+
+        foreach (self::constants() as $name => $constant) {
+            if (array_key_exists($name, self::$values[static::class])) {
+                continue;
+            }
+
+            static::createValue($name, $constant[0], $constant[1]);
+        }
+
+        uasort(self::$values[static::class], function (self $a, self $b) {
+            return $a->ordinal() <=> $b->ordinal();
+        });
+
+        self::$allValuesLoaded[static::class] = true;
+        return self::$values[static::class];
+    }
+
+    private static function constants() : array
+    {
+        if (isset(self::$constants[static::class])) {
+            return self::$constants[static::class];
+        }
+
+        self::$constants[static::class] = [];
+        $reflectionClass = new ReflectionClass(static::class);
+        $ordinal = -1;
+
+        foreach ($reflectionClass->getReflectionConstants() as $reflectionConstant) {
+            if (! $reflectionConstant->isProtected()) {
+                continue;
+            }
+
+            $value = $reflectionConstant->getValue();
+
+            self::$constants[static::class][$reflectionConstant->name] = [
+                ++$ordinal,
+                is_array($value) ? $value : []
+            ];
+        }
+
+        return self::$constants[static::class];
+    }
+
+    /**
+     * Returns the name of this enum constant, exactly as declared in its enum declaration.
+     *
+     * Most programmers should use the {@see self::__toString()} method in preference to this one, as the toString
+     * method may return a more user-friendly name. This method is designed primarily for use in specialized situations
+     * where correctness depends on getting the exact name, which will not vary from release to release.
+     */
+    final public function name() : string
+    {
+        return $this->name;
+    }
+
+    /**
+     * Returns the ordinal of this enumeration constant (its position in its enum declaration, where the initial
+     * constant is assigned an ordinal of zero).
+     *
+     * Most programmers will have no use for this method. It is designed for use by sophisticated enum-based data
+     * structures.
+     */
+    final public function ordinal() : int
+    {
+        return $this->ordinal;
+    }
+
+    /**
+     * Compares this enum with the specified object for order.
+     *
+     * Returns negative integer, zero or positive integer as this object is less than, equal to or greater than the
+     * specified object.
+     *
+     * Enums are only comparable to other enums of the same type. The natural order implemented by this method is the
+     * order in which the constants are declared.
+     *
+     * @throws MismatchException if the passed enum is not of the same type
+     */
+    final public function compareTo(self $other) : int
+    {
+        if (! $other instanceof static) {
+            throw new MismatchException(sprintf(
+                'The passed enum %s is not of the same type as %s',
+                get_class($other),
+                static::class
+            ));
+        }
+
+        return $this->ordinal - $other->ordinal;
+    }
+
+    /**
+     * Forbid cloning enums.
+     *
+     * @throws CloneNotSupportedException
+     */
+    final public function __clone()
+    {
+        throw new CloneNotSupportedException();
+    }
+
+    /**
+     * Forbid serializing enums.
+     *
+     * @throws SerializeNotSupportedException
+     */
+    final public function __sleep() : array
+    {
+        throw new SerializeNotSupportedException();
+    }
+
+    /**
+     * Forbid unserializing enums.
+     *
+     * @throws UnserializeNotSupportedException
+     */
+    final public function __wakeup() : void
+    {
+        throw new UnserializeNotSupportedException();
+    }
+
+    /**
+     * Turns the enum into a string representation.
+     *
+     * You may override this method to give a more user-friendly version.
+     */
+    public function __toString() : string
+    {
+        return $this->name;
+    }
+}

+ 385 - 0
htdocs/includes/dasprid/enum/src/EnumMap.php

@@ -0,0 +1,385 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum;
+
+use DASPRiD\Enum\Exception\ExpectationException;
+use DASPRiD\Enum\Exception\IllegalArgumentException;
+use IteratorAggregate;
+use Serializable;
+use Traversable;
+
+/**
+ * A specialized map implementation for use with enum type keys.
+ *
+ * All of the keys in an enum map must come from a single enum type that is specified, when the map is created. Enum
+ * maps are represented internally as arrays. This representation is extremely compact and efficient.
+ *
+ * Enum maps are maintained in the natural order of their keys (the order in which the enum constants are declared).
+ * This is reflected in the iterators returned by the collection views {@see self::getIterator()} and
+ * {@see self::values()}.
+ *
+ * Iterators returned by the collection views are not consistent: They may or may not show the effects of modifications
+ * to the map that occur while the iteration is in progress.
+ */
+final class EnumMap implements Serializable, IteratorAggregate
+{
+    /**
+     * The class name of the key.
+     *
+     * @var string
+     */
+    private $keyType;
+
+    /**
+     * The type of the value.
+     *
+     * @var string
+     */
+    private $valueType;
+
+    /**
+     * @var bool
+     */
+    private $allowNullValues;
+
+    /**
+     * All of the constants comprising the enum, cached for performance.
+     *
+     * @var array<int, AbstractEnum>
+     */
+    private $keyUniverse;
+
+    /**
+     * Array representation of this map. The ith element is the value to which universe[i] is currently mapped, or null
+     * if it isn't mapped to anything, or NullValue if it's mapped to null.
+     *
+     * @var array<int, mixed>
+     */
+    private $values;
+
+    /**
+     * @var int
+     */
+    private $size = 0;
+
+    /**
+     * Creates a new enum map.
+     *
+     * @param string $keyType the type of the keys, must extend AbstractEnum
+     * @param string $valueType the type of the values
+     * @param bool $allowNullValues whether to allow null values
+     * @throws IllegalArgumentException when key type does not extend AbstractEnum
+     */
+    public function __construct(string $keyType, string $valueType, bool $allowNullValues)
+    {
+        if (! is_subclass_of($keyType, AbstractEnum::class)) {
+            throw new IllegalArgumentException(sprintf(
+                'Class %s does not extend %s',
+                $keyType,
+                AbstractEnum::class
+            ));
+        }
+
+        $this->keyType = $keyType;
+        $this->valueType = $valueType;
+        $this->allowNullValues = $allowNullValues;
+        $this->keyUniverse = $keyType::values();
+        $this->values = array_fill(0, count($this->keyUniverse), null);
+    }
+
+    public function __serialize(): array
+    {
+        $values = [];
+
+        foreach ($this->values as $ordinal => $value) {
+            if (null === $value) {
+                continue;
+            }
+
+            $values[$ordinal] = $this->unmaskNull($value);
+        }
+
+        return [
+            'keyType' => $this->keyType,
+            'valueType' => $this->valueType,
+            'allowNullValues' => $this->allowNullValues,
+            'values' => $values,
+        ];
+    }
+
+    public function __unserialize(array $data): void
+    {
+        $this->unserialize(serialize($data));
+    }
+
+    /**
+     * Checks whether the map types match the supplied ones.
+     *
+     * You should call this method when an EnumMap is passed to you and you want to ensure that it's made up of the
+     * correct types.
+     *
+     * @throws ExpectationException when supplied key type mismatches local key type
+     * @throws ExpectationException when supplied value type mismatches local value type
+     * @throws ExpectationException when the supplied map allows null values, abut should not
+     */
+    public function expect(string $keyType, string $valueType, bool $allowNullValues) : void
+    {
+        if ($keyType !== $this->keyType) {
+            throw new ExpectationException(sprintf(
+                'Callee expected an EnumMap with key type %s, but got %s',
+                $keyType,
+                $this->keyType
+            ));
+        }
+
+        if ($valueType !== $this->valueType) {
+            throw new ExpectationException(sprintf(
+                'Callee expected an EnumMap with value type %s, but got %s',
+                $keyType,
+                $this->keyType
+            ));
+        }
+
+        if ($allowNullValues !== $this->allowNullValues) {
+            throw new ExpectationException(sprintf(
+                'Callee expected an EnumMap with nullable flag %s, but got %s',
+                ($allowNullValues ? 'true' : 'false'),
+                ($this->allowNullValues ? 'true' : 'false')
+            ));
+        }
+    }
+
+    /**
+     * Returns the number of key-value mappings in this map.
+     */
+    public function size() : int
+    {
+        return $this->size;
+    }
+
+    /**
+     * Returns true if this map maps one or more keys to the specified value.
+     */
+    public function containsValue($value) : bool
+    {
+        return in_array($this->maskNull($value), $this->values, true);
+    }
+
+    /**
+     * Returns true if this map contains a mapping for the specified key.
+     */
+    public function containsKey(AbstractEnum $key) : bool
+    {
+        $this->checkKeyType($key);
+        return null !== $this->values[$key->ordinal()];
+    }
+
+    /**
+     * Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.
+     *
+     * More formally, if this map contains a mapping from a key to a value, then this method returns the value;
+     * otherwise it returns null (there can be at most one such mapping).
+     *
+     * A return value of null does not necessarily indicate that the map contains no mapping for the key; it's also
+     * possible that hte map explicitly maps the key to null. The {@see self::containsKey()} operation may be used to
+     * distinguish these two cases.
+     *
+     * @return mixed
+     */
+    public function get(AbstractEnum $key)
+    {
+        $this->checkKeyType($key);
+        return $this->unmaskNull($this->values[$key->ordinal()]);
+    }
+
+    /**
+     * Associates the specified value with the specified key in this map.
+     *
+     * If the map previously contained a mapping for this key, the old value is replaced.
+     *
+     * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key.
+     *               (a null return can also indicate that the map previously associated null with the specified key.)
+     * @throws IllegalArgumentException when the passed values does not match the internal value type
+     */
+    public function put(AbstractEnum $key, $value)
+    {
+        $this->checkKeyType($key);
+
+        if (! $this->isValidValue($value)) {
+            throw new IllegalArgumentException(sprintf('Value is not of type %s', $this->valueType));
+        }
+
+        $index = $key->ordinal();
+        $oldValue = $this->values[$index];
+        $this->values[$index] = $this->maskNull($value);
+
+        if (null === $oldValue) {
+            ++$this->size;
+        }
+
+        return $this->unmaskNull($oldValue);
+    }
+
+    /**
+     * Removes the mapping for this key frm this map if present.
+     *
+     * @return mixed the previous value associated with the specified key, or null if there was no mapping for the key.
+     *               (a null return can also indicate that the map previously associated null with the specified key.)
+     */
+    public function remove(AbstractEnum $key)
+    {
+        $this->checkKeyType($key);
+
+        $index = $key->ordinal();
+        $oldValue = $this->values[$index];
+        $this->values[$index] = null;
+
+        if (null !== $oldValue) {
+            --$this->size;
+        }
+
+        return $this->unmaskNull($oldValue);
+    }
+
+    /**
+     * Removes all mappings from this map.
+     */
+    public function clear() : void
+    {
+        $this->values = array_fill(0, count($this->keyUniverse), null);
+        $this->size = 0;
+    }
+
+    /**
+     * Compares the specified map with this map for quality.
+     *
+     * Returns true if the two maps represent the same mappings.
+     */
+    public function equals(self $other) : bool
+    {
+        if ($this === $other) {
+            return true;
+        }
+
+        if ($this->size !== $other->size) {
+            return false;
+        }
+
+        return $this->values === $other->values;
+    }
+
+    /**
+     * Returns the values contained in this map.
+     *
+     * The array will contain the values in the order their corresponding keys appear in the map, which is their natural
+     * order (the order in which the num constants are declared).
+     */
+    public function values() : array
+    {
+        return array_values(array_map(function ($value) {
+            return $this->unmaskNull($value);
+        }, array_filter($this->values, function ($value) : bool {
+            return null !== $value;
+        })));
+    }
+
+    public function serialize() : string
+    {
+        return serialize($this->__serialize());
+    }
+
+    public function unserialize($serialized) : void
+    {
+        $data = unserialize($serialized);
+        $this->__construct($data['keyType'], $data['valueType'], $data['allowNullValues']);
+
+        foreach ($this->keyUniverse as $key) {
+            if (array_key_exists($key->ordinal(), $data['values'])) {
+                $this->put($key, $data['values'][$key->ordinal()]);
+            }
+        }
+    }
+
+    public function getIterator() : Traversable
+    {
+        foreach ($this->keyUniverse as $key) {
+            if (null === $this->values[$key->ordinal()]) {
+                continue;
+            }
+
+            yield $key => $this->unmaskNull($this->values[$key->ordinal()]);
+        }
+    }
+
+    private function maskNull($value)
+    {
+        if (null === $value) {
+            return NullValue::instance();
+        }
+
+        return $value;
+    }
+
+    private function unmaskNull($value)
+    {
+        if ($value instanceof NullValue) {
+            return null;
+        }
+
+        return $value;
+    }
+
+    /**
+     * @throws IllegalArgumentException when the passed key does not match the internal key type
+     */
+    private function checkKeyType(AbstractEnum $key) : void
+    {
+        if (get_class($key) !== $this->keyType) {
+            throw new IllegalArgumentException(sprintf(
+                'Object of type %s is not the same type as %s',
+                get_class($key),
+                $this->keyType
+            ));
+        }
+    }
+
+    private function isValidValue($value) : bool
+    {
+        if (null === $value) {
+            if ($this->allowNullValues) {
+                return true;
+            }
+
+            return false;
+        }
+
+        switch ($this->valueType) {
+            case 'mixed':
+                return true;
+
+            case 'bool':
+            case 'boolean':
+                return is_bool($value);
+
+            case 'int':
+            case 'integer':
+                return is_int($value);
+
+            case 'float':
+            case 'double':
+                return is_float($value);
+
+            case 'string':
+                return is_string($value);
+
+            case 'object':
+                return is_object($value);
+
+            case 'array':
+                return is_array($value);
+        }
+
+        return $value instanceof $this->valueType;
+    }
+}

+ 10 - 0
htdocs/includes/dasprid/enum/src/Exception/CloneNotSupportedException.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum\Exception;
+
+use Exception;
+
+final class CloneNotSupportedException extends Exception implements ExceptionInterface
+{
+}

+ 10 - 0
htdocs/includes/dasprid/enum/src/Exception/ExceptionInterface.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum\Exception;
+
+use Throwable;
+
+interface ExceptionInterface extends Throwable
+{
+}

+ 10 - 0
htdocs/includes/dasprid/enum/src/Exception/ExpectationException.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum\Exception;
+
+use Exception;
+
+final class ExpectationException extends Exception implements ExceptionInterface
+{
+}

+ 10 - 0
htdocs/includes/dasprid/enum/src/Exception/IllegalArgumentException.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum\Exception;
+
+use Exception;
+
+final class IllegalArgumentException extends Exception implements ExceptionInterface
+{
+}

+ 10 - 0
htdocs/includes/dasprid/enum/src/Exception/MismatchException.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum\Exception;
+
+use Exception;
+
+final class MismatchException extends Exception implements ExceptionInterface
+{
+}

+ 10 - 0
htdocs/includes/dasprid/enum/src/Exception/SerializeNotSupportedException.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum\Exception;
+
+use Exception;
+
+final class SerializeNotSupportedException extends Exception implements ExceptionInterface
+{
+}

+ 10 - 0
htdocs/includes/dasprid/enum/src/Exception/UnserializeNotSupportedException.php

@@ -0,0 +1,10 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum\Exception;
+
+use Exception;
+
+final class UnserializeNotSupportedException extends Exception implements ExceptionInterface
+{
+}

+ 55 - 0
htdocs/includes/dasprid/enum/src/NullValue.php

@@ -0,0 +1,55 @@
+<?php
+declare(strict_types = 1);
+
+namespace DASPRiD\Enum;
+
+use DASPRiD\Enum\Exception\CloneNotSupportedException;
+use DASPRiD\Enum\Exception\SerializeNotSupportedException;
+use DASPRiD\Enum\Exception\UnserializeNotSupportedException;
+
+final class NullValue
+{
+    /**
+     * @var self
+     */
+    private static $instance;
+
+    private function __construct()
+    {
+    }
+
+    public static function instance() : self
+    {
+        return self::$instance ?: self::$instance = new self();
+    }
+
+    /**
+     * Forbid cloning enums.
+     *
+     * @throws CloneNotSupportedException
+     */
+    final public function __clone()
+    {
+        throw new CloneNotSupportedException();
+    }
+
+    /**
+     * Forbid serializing enums.
+     *
+     * @throws SerializeNotSupportedException
+     */
+    final public function __sleep() : array
+    {
+        throw new SerializeNotSupportedException();
+    }
+
+    /**
+     * Forbid unserializing enums.
+     *
+     * @throws UnserializeNotSupportedException
+     */
+    final public function __wakeup() : void
+    {
+        throw new UnserializeNotSupportedException();
+    }
+}

+ 2 - 0
htdocs/includes/endroid/qr-code/.gitattributes

@@ -0,0 +1,2 @@
+/.github/workflows/ export-ignore
+/tests/ export-ignore

+ 1 - 0
htdocs/includes/endroid/qr-code/.github/FUNDING.yml

@@ -0,0 +1 @@
+github: endroid

+ 4 - 0
htdocs/includes/endroid/qr-code/.gitignore

@@ -0,0 +1,4 @@
+/composer
+/composer.lock
+/tests/coverage/
+/vendor/

+ 19 - 0
htdocs/includes/endroid/qr-code/LICENSE

@@ -0,0 +1,19 @@
+Copyright 2022 (c) Jeroen van den Enden
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 210 - 0
htdocs/includes/endroid/qr-code/README.md

@@ -0,0 +1,210 @@
+# QR Code
+
+*By [endroid](https://endroid.nl/)*
+
+[![Latest Stable Version](http://img.shields.io/packagist/v/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
+[![Build Status](https://github.com/endroid/qr-code/workflows/CI/badge.svg)](https://github.com/endroid/qr-code/actions)
+[![Total Downloads](http://img.shields.io/packagist/dt/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
+[![Monthly Downloads](http://img.shields.io/packagist/dm/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
+[![License](http://img.shields.io/packagist/l/endroid/qr-code.svg)](https://packagist.org/packages/endroid/qr-code)
+
+This library helps you generate QR codes in a jiffy. Makes use of [bacon/bacon-qr-code](https://github.com/Bacon/BaconQrCode)
+to generate the matrix and [khanamiryan/qrcode-detector-decoder](https://github.com/khanamiryan/php-qrcode-detector-decoder)
+for validating generated QR codes. Further extended with Twig extensions, generation routes, a factory and a
+Symfony bundle for easy installation and configuration. Different writers are provided to generate the QR code
+as PNG, SVG, EPS or in binary format.
+
+## Sponsored by
+
+[![Blackfire.io](assets/blackfire.png)](https://www.blackfire.io)
+
+## Installation
+
+Use [Composer](https://getcomposer.org/) to install the library. Also make sure you have enabled and configured the
+[GD extension](https://www.php.net/manual/en/book.image.php) if you want to generate images.
+
+``` bash
+ composer require endroid/qr-code
+```
+
+## Usage: using the builder
+
+```php
+use Endroid\QrCode\Builder\Builder;
+use Endroid\QrCode\Encoding\Encoding;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
+use Endroid\QrCode\Label\Alignment\LabelAlignmentCenter;
+use Endroid\QrCode\Label\Font\NotoSans;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
+use Endroid\QrCode\Writer\PngWriter;
+
+$result = Builder::create()
+    ->writer(new PngWriter())
+    ->writerOptions([])
+    ->data('Custom QR code contents')
+    ->encoding(new Encoding('UTF-8'))
+    ->errorCorrectionLevel(new ErrorCorrectionLevelHigh())
+    ->size(300)
+    ->margin(10)
+    ->roundBlockSizeMode(new RoundBlockSizeModeMargin())
+    ->logoPath(__DIR__.'/assets/symfony.png')
+    ->labelText('This is the label')
+    ->labelFont(new NotoSans(20))
+    ->labelAlignment(new LabelAlignmentCenter())
+    ->validateResult(false)
+    ->build();
+```
+
+## Usage: without using the builder
+
+```php
+use Endroid\QrCode\Color\Color;
+use Endroid\QrCode\Encoding\Encoding;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
+use Endroid\QrCode\QrCode;
+use Endroid\QrCode\Label\Label;
+use Endroid\QrCode\Logo\Logo;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeMargin;
+use Endroid\QrCode\Writer\PngWriter;
+use Endroid\QrCode\Writer\ValidationException;
+
+$writer = new PngWriter();
+
+// Create QR code
+$qrCode = QrCode::create('Life is too short to be generating QR codes')
+    ->setEncoding(new Encoding('UTF-8'))
+    ->setErrorCorrectionLevel(new ErrorCorrectionLevelLow())
+    ->setSize(300)
+    ->setMargin(10)
+    ->setRoundBlockSizeMode(new RoundBlockSizeModeMargin())
+    ->setForegroundColor(new Color(0, 0, 0))
+    ->setBackgroundColor(new Color(255, 255, 255));
+
+// Create generic logo
+$logo = Logo::create(__DIR__.'/assets/symfony.png')
+    ->setResizeToWidth(50);
+
+// Create generic label
+$label = Label::create('Label')
+    ->setTextColor(new Color(255, 0, 0));
+
+$result = $writer->write($qrCode, $logo, $label);
+
+// Validate the result
+$writer->validateResult($result, 'Life is too short to be generating QR codes');
+```
+
+## Usage: working with results
+
+```php
+
+// Directly output the QR code
+header('Content-Type: '.$result->getMimeType());
+echo $result->getString();
+
+// Save it to a file
+$result->saveToFile(__DIR__.'/qrcode.png');
+
+// Generate a data URI to include image data inline (i.e. inside an <img> tag)
+$dataUri = $result->getDataUri();
+```
+
+![QR Code](https://endroid.nl/qr-code/default/Life%20is%20too%20short%20to%20be%20generating%20QR%20codes)
+
+### Writer options
+
+Some writers provide writer options. Each available writer option is can be
+found as a constant prefixed with WRITER_OPTION_ in the writer class.
+
+* `PdfWriter`
+  * `unit`: unit of measurement (default: mm)
+  * `fpdf`: PDF to place the image in (default: new PDF)
+  * `x`: image offset (default: 0)
+  * `y`: image offset (default: 0)
+* `PngWriter`
+  * `compression_level`: compression level (0-9, default: -1 = zlib default)
+* `SvgWriter`
+  * `block_id`: id of the block element for external reference (default: block)
+  * `exclude_xml_declaration`: exclude XML declaration (default: false)
+  * `exclude_svg_width_and_height`: exclude width and height (default: false)
+  * `force_xlink_href`: forces xlink namespace in case of compatibility issues (default: false)
+* `WebPWriter`
+  * `quality`: image quality (0-100, default: 80)
+
+You can provide any writer options like this.
+
+```php
+use Endroid\QrCode\Writer\SvgWriter;
+
+$builder->setWriterOptions([
+    SvgWriter::WRITER_OPTION_EXCLUDE_XML_DECLARATION => true
+]);
+```
+
+### Encoding
+
+If you use a barcode scanner you can have some troubles while reading the
+generated QR codes. Depending on the encoding you chose you will have an extra
+amount of data corresponding to the ECI block. Some barcode scanner are not
+programmed to interpret this block of information. To ensure a maximum
+compatibility you can use the `ISO-8859-1` encoding that is the default
+encoding used by barcode scanners (if your character set supports it,
+i.e. no Chinese characters are present).
+
+### Round block size mode
+
+By default block sizes are rounded to guarantee sharp images and improve
+readability. However some other rounding variants are available.
+
+* `margin (default)`: the size of the QR code is shrunk if necessary but the size
+  of the final image remains unchanged due to additional margin being added.
+* `enlarge`: the size of the QR code and the final image are enlarged when
+  rounding differences occur.
+* `shrink`: the size of the QR code and the final image are
+  shrunk when rounding differences occur.
+* `none`: No rounding. This mode can be used when blocks don't need to be rounded
+  to pixels (for instance SVG).
+
+## Readability
+
+The readability of a QR code is primarily determined by the size, the input
+length, the error correction level and any possible logo over the image so you
+can tweak these parameters if you are looking for optimal results. You can also
+check $qrCode->getRoundBlockSize() value to see if block dimensions are rounded
+so that the image is more sharp and readable. Please note that rounding block
+size can result in additional padding to compensate for the rounding difference.
+And finally the encoding (default UTF-8 to support large character sets) can be
+set to `ISO-8859-1` if possible to improve readability.
+
+## Validating the generated QR code
+
+If you need to be extra sure the QR code you generated is readable and contains
+the exact data you requested you can enable the validation reader, which is
+disabled by default. You can do this either via the builder or directly on any
+writer that supports validation. See the examples above.
+
+Please note that validation affects performance so only use it in case of problems.
+
+## Symfony integration
+
+The [endroid/qr-code-bundle](https://github.com/endroid/qr-code-bundle)
+integrates the QR code library in Symfony for an even better experience.
+
+* Configure your defaults (like image size, default writer etc.)
+* Support for multiple configurations and injection via aliases
+* Generate QR codes for defined configurations via URL like /qr-code/<config>/Hello
+* Generate QR codes or URLs directly from Twig using dedicated functions
+ 
+Read the [bundle documentation](https://github.com/endroid/qr-code-bundle)
+for more information.
+
+## Versioning
+
+Version numbers follow the MAJOR.MINOR.PATCH scheme. Backwards compatibility
+breaking changes will be kept to a minimum but be aware that these can occur.
+Lock your dependencies for production and test your code when upgrading.
+
+## License
+
+This bundle is under the MIT license. For the full copyright and license
+information please view the LICENSE file that was distributed with this source code.

BIN
htdocs/includes/endroid/qr-code/assets/blackfire.png


BIN
htdocs/includes/endroid/qr-code/assets/noto_sans.otf


BIN
htdocs/includes/endroid/qr-code/assets/open_sans.ttf


+ 57 - 0
htdocs/includes/endroid/qr-code/composer.json.disabled

@@ -0,0 +1,57 @@
+{
+    "name": "endroid/qr-code",
+    "description": "Endroid QR Code",
+    "keywords": ["endroid", "qrcode", "qr", "code", "php"],
+    "homepage": "https://github.com/endroid/qr-code",
+    "type": "library",
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "Jeroen van den Enden",
+            "email": "info@endroid.nl"
+        }
+    ],
+    "require": {
+        "php": "^8.0",
+        "bacon/bacon-qr-code": "^2.0.5"
+    },
+    "require-dev": {
+        "ext-gd": "*",
+        "endroid/quality": "dev-master",
+        "khanamiryan/qrcode-detector-decoder": "^1.0.4||^2.0.2",
+        "setasign/fpdf": "^1.8.2"
+    },
+    "conflict": {
+        "khanamiryan/qrcode-detector-decoder": "^1.0.6"
+    },
+    "suggest": {
+        "ext-gd": "Enables you to write PNG images",
+        "khanamiryan/qrcode-detector-decoder": "Enables you to use the image validator",
+        "roave/security-advisories": "Makes sure package versions with known security issues are not installed",
+        "setasign/fpdf": "Enables you to use the PDF writer"
+    },
+    "autoload": {
+        "psr-4": {
+            "Endroid\\QrCode\\": "src/"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "Endroid\\QrCode\\Tests\\": "tests/"
+        }
+    },
+    "config": {
+        "sort-packages": true,
+        "preferred-install": {
+            "endroid/*": "source"
+        },
+        "allow-plugins": {
+            "endroid/installer": true
+        }
+    },
+    "extra": {
+        "branch-alias": {
+            "dev-master": "4.x-dev"
+        }
+    }
+}

+ 30 - 0
htdocs/includes/endroid/qr-code/src/Bacon/ErrorCorrectionLevelConverter.php

@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Bacon;
+
+use BaconQrCode\Common\ErrorCorrectionLevel;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelHigh;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelLow;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelMedium;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelQuartile;
+
+final class ErrorCorrectionLevelConverter
+{
+    public static function convertToBaconErrorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): ErrorCorrectionLevel
+    {
+        if ($errorCorrectionLevel instanceof ErrorCorrectionLevelLow) {
+            return ErrorCorrectionLevel::valueOf('L');
+        } elseif ($errorCorrectionLevel instanceof ErrorCorrectionLevelMedium) {
+            return ErrorCorrectionLevel::valueOf('M');
+        } elseif ($errorCorrectionLevel instanceof ErrorCorrectionLevelQuartile) {
+            return ErrorCorrectionLevel::valueOf('Q');
+        } elseif ($errorCorrectionLevel instanceof ErrorCorrectionLevelHigh) {
+            return ErrorCorrectionLevel::valueOf('H');
+        }
+
+        throw new \Exception('Error correction level could not be converted');
+    }
+}

+ 32 - 0
htdocs/includes/endroid/qr-code/src/Bacon/MatrixFactory.php

@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Bacon;
+
+use BaconQrCode\Encoder\Encoder;
+use Endroid\QrCode\Matrix\Matrix;
+use Endroid\QrCode\Matrix\MatrixFactoryInterface;
+use Endroid\QrCode\Matrix\MatrixInterface;
+use Endroid\QrCode\QrCodeInterface;
+
+final class MatrixFactory implements MatrixFactoryInterface
+{
+    public function create(QrCodeInterface $qrCode): MatrixInterface
+    {
+        $baconErrorCorrectionLevel = ErrorCorrectionLevelConverter::convertToBaconErrorCorrectionLevel($qrCode->getErrorCorrectionLevel());
+        $baconMatrix = Encoder::encode($qrCode->getData(), $baconErrorCorrectionLevel, strval($qrCode->getEncoding()))->getMatrix();
+
+        $blockValues = [];
+        $columnCount = $baconMatrix->getWidth();
+        $rowCount = $baconMatrix->getHeight();
+        for ($rowIndex = 0; $rowIndex < $rowCount; ++$rowIndex) {
+            $blockValues[$rowIndex] = [];
+            for ($columnIndex = 0; $columnIndex < $columnCount; ++$columnIndex) {
+                $blockValues[$rowIndex][$columnIndex] = $baconMatrix->get($columnIndex, $rowIndex);
+            }
+        }
+
+        return new Matrix($blockValues, $qrCode->getSize(), $qrCode->getMargin(), $qrCode->getRoundBlockSizeMode());
+    }
+}

+ 279 - 0
htdocs/includes/endroid/qr-code/src/Builder/Builder.php

@@ -0,0 +1,279 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Builder;
+
+use Endroid\QrCode\Color\ColorInterface;
+use Endroid\QrCode\Encoding\EncodingInterface;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
+use Endroid\QrCode\Exception\ValidationException;
+use Endroid\QrCode\Label\Alignment\LabelAlignmentInterface;
+use Endroid\QrCode\Label\Font\FontInterface;
+use Endroid\QrCode\Label\Label;
+use Endroid\QrCode\Label\LabelInterface;
+use Endroid\QrCode\Label\Margin\MarginInterface;
+use Endroid\QrCode\Logo\Logo;
+use Endroid\QrCode\Logo\LogoInterface;
+use Endroid\QrCode\QrCode;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
+use Endroid\QrCode\Writer\PngWriter;
+use Endroid\QrCode\Writer\Result\ResultInterface;
+use Endroid\QrCode\Writer\ValidatingWriterInterface;
+use Endroid\QrCode\Writer\WriterInterface;
+
+class Builder implements BuilderInterface
+{
+    /**
+     * @var array<string, mixed>{
+     *     data: string,
+     *     writer: WriterInterface,
+     *     writerOptions: array,
+     *     qrCodeClass: class-string,
+     *     logoClass: class-string,
+     *     labelClass: class-string,
+     *     validateResult: bool,
+     *     size?: int,
+     *     encoding?: EncodingInterface,
+     *     errorCorrectionLevel?: ErrorCorrectionLevelInterface,
+     *     roundBlockSizeMode?: RoundBlockSizeModeInterface,
+     *     margin?: int,
+     *     backgroundColor?: ColorInterface,
+     *     foregroundColor?: ColorInterface,
+     *     labelText?: string,
+     *     labelFont?: FontInterface,
+     *     labelAlignment?: LabelAlignmentInterface,
+     *     labelMargin?: MarginInterface,
+     *     labelTextColor?: ColorInterface,
+     *     logoPath?: string,
+     *     logoResizeToWidth?: int,
+     *     logoResizeToHeight?: int,
+     *     logoPunchoutBackground?: bool
+     * }
+     */
+    private array $options;
+
+    public function __construct()
+    {
+        $this->options = [
+            'data' => '',
+            'writer' => new PngWriter(),
+            'writerOptions' => [],
+            'qrCodeClass' => QrCode::class,
+            'logoClass' => Logo::class,
+            'labelClass' => Label::class,
+            'validateResult' => false,
+        ];
+    }
+
+    public static function create(): BuilderInterface
+    {
+        return new self();
+    }
+
+    public function writer(WriterInterface $writer): BuilderInterface
+    {
+        $this->options['writer'] = $writer;
+
+        return $this;
+    }
+
+    /** @param array<string, mixed> $writerOptions */
+    public function writerOptions(array $writerOptions): BuilderInterface
+    {
+        $this->options['writerOptions'] = $writerOptions;
+
+        return $this;
+    }
+
+    public function data(string $data): BuilderInterface
+    {
+        $this->options['data'] = $data;
+
+        return $this;
+    }
+
+    public function encoding(EncodingInterface $encoding): BuilderInterface
+    {
+        $this->options['encoding'] = $encoding;
+
+        return $this;
+    }
+
+    public function errorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): BuilderInterface
+    {
+        $this->options['errorCorrectionLevel'] = $errorCorrectionLevel;
+
+        return $this;
+    }
+
+    public function size(int $size): BuilderInterface
+    {
+        $this->options['size'] = $size;
+
+        return $this;
+    }
+
+    public function margin(int $margin): BuilderInterface
+    {
+        $this->options['margin'] = $margin;
+
+        return $this;
+    }
+
+    public function roundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): BuilderInterface
+    {
+        $this->options['roundBlockSizeMode'] = $roundBlockSizeMode;
+
+        return $this;
+    }
+
+    public function foregroundColor(ColorInterface $foregroundColor): BuilderInterface
+    {
+        $this->options['foregroundColor'] = $foregroundColor;
+
+        return $this;
+    }
+
+    public function backgroundColor(ColorInterface $backgroundColor): BuilderInterface
+    {
+        $this->options['backgroundColor'] = $backgroundColor;
+
+        return $this;
+    }
+
+    public function logoPath(string $logoPath): BuilderInterface
+    {
+        $this->options['logoPath'] = $logoPath;
+
+        return $this;
+    }
+
+    public function logoResizeToWidth(int $logoResizeToWidth): BuilderInterface
+    {
+        $this->options['logoResizeToWidth'] = $logoResizeToWidth;
+
+        return $this;
+    }
+
+    public function logoResizeToHeight(int $logoResizeToHeight): BuilderInterface
+    {
+        $this->options['logoResizeToHeight'] = $logoResizeToHeight;
+
+        return $this;
+    }
+
+    public function logoPunchoutBackground(bool $logoPunchoutBackground): BuilderInterface
+    {
+        $this->options['logoPunchoutBackground'] = $logoPunchoutBackground;
+
+        return $this;
+    }
+
+    public function labelText(string $labelText): BuilderInterface
+    {
+        $this->options['labelText'] = $labelText;
+
+        return $this;
+    }
+
+    public function labelFont(FontInterface $labelFont): BuilderInterface
+    {
+        $this->options['labelFont'] = $labelFont;
+
+        return $this;
+    }
+
+    public function labelAlignment(LabelAlignmentInterface $labelAlignment): BuilderInterface
+    {
+        $this->options['labelAlignment'] = $labelAlignment;
+
+        return $this;
+    }
+
+    public function labelMargin(MarginInterface $labelMargin): BuilderInterface
+    {
+        $this->options['labelMargin'] = $labelMargin;
+
+        return $this;
+    }
+
+    public function labelTextColor(ColorInterface $labelTextColor): BuilderInterface
+    {
+        $this->options['labelTextColor'] = $labelTextColor;
+
+        return $this;
+    }
+
+    public function validateResult(bool $validateResult): BuilderInterface
+    {
+        $this->options['validateResult'] = $validateResult;
+
+        return $this;
+    }
+
+    public function build(): ResultInterface
+    {
+        $writer = $this->options['writer'];
+
+        if ($this->options['validateResult'] && !$writer instanceof ValidatingWriterInterface) {
+            throw ValidationException::createForUnsupportedWriter(strval(get_class($writer)));
+        }
+
+        /** @var QrCode $qrCode */
+        $qrCode = $this->buildObject($this->options['qrCodeClass']);
+
+        /** @var LogoInterface|null $logo */
+        $logo = $this->buildObject($this->options['logoClass'], 'logo');
+
+        /** @var LabelInterface|null $label */
+        $label = $this->buildObject($this->options['labelClass'], 'label');
+
+        $result = $writer->write($qrCode, $logo, $label, $this->options['writerOptions']);
+
+        if ($this->options['validateResult'] && $writer instanceof ValidatingWriterInterface) {
+            $writer->validateResult($result, $qrCode->getData());
+        }
+
+        return $result;
+    }
+
+    /**
+     * @param class-string $class
+     *
+     * @return mixed
+     */
+    private function buildObject(string $class, string|null $optionsPrefix = null)
+    {
+        /** @var \ReflectionClass<object> $reflectionClass */
+        $reflectionClass = new \ReflectionClass($class);
+
+        $arguments = [];
+        $hasBuilderOptions = false;
+        $missingRequiredArguments = [];
+        /** @var \ReflectionMethod $constructor */
+        $constructor = $reflectionClass->getConstructor();
+        $constructorParameters = $constructor->getParameters();
+        foreach ($constructorParameters as $parameter) {
+            $optionName = null === $optionsPrefix ? $parameter->getName() : $optionsPrefix.ucfirst($parameter->getName());
+            if (isset($this->options[$optionName])) {
+                $hasBuilderOptions = true;
+                $arguments[] = $this->options[$optionName];
+            } elseif ($parameter->isDefaultValueAvailable()) {
+                $arguments[] = $parameter->getDefaultValue();
+            } else {
+                $missingRequiredArguments[] = $optionName;
+            }
+        }
+
+        if (!$hasBuilderOptions) {
+            return null;
+        }
+
+        if (count($missingRequiredArguments) > 0) {
+            throw new \Exception(sprintf('Missing required arguments: %s', implode(', ', $missingRequiredArguments)));
+        }
+
+        return $reflectionClass->newInstanceArgs($arguments);
+    }
+}

+ 63 - 0
htdocs/includes/endroid/qr-code/src/Builder/BuilderInterface.php

@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Builder;
+
+use Endroid\QrCode\Color\ColorInterface;
+use Endroid\QrCode\Encoding\EncodingInterface;
+use Endroid\QrCode\ErrorCorrectionLevel\ErrorCorrectionLevelInterface;
+use Endroid\QrCode\Label\Alignment\LabelAlignmentInterface;
+use Endroid\QrCode\Label\Font\FontInterface;
+use Endroid\QrCode\Label\Margin\MarginInterface;
+use Endroid\QrCode\RoundBlockSizeMode\RoundBlockSizeModeInterface;
+use Endroid\QrCode\Writer\Result\ResultInterface;
+use Endroid\QrCode\Writer\WriterInterface;
+
+interface BuilderInterface
+{
+    public static function create(): BuilderInterface;
+
+    public function writer(WriterInterface $writer): BuilderInterface;
+
+    /** @param array<string, mixed> $writerOptions */
+    public function writerOptions(array $writerOptions): BuilderInterface;
+
+    public function data(string $data): BuilderInterface;
+
+    public function encoding(EncodingInterface $encoding): BuilderInterface;
+
+    public function errorCorrectionLevel(ErrorCorrectionLevelInterface $errorCorrectionLevel): BuilderInterface;
+
+    public function size(int $size): BuilderInterface;
+
+    public function margin(int $margin): BuilderInterface;
+
+    public function roundBlockSizeMode(RoundBlockSizeModeInterface $roundBlockSizeMode): BuilderInterface;
+
+    public function foregroundColor(ColorInterface $foregroundColor): BuilderInterface;
+
+    public function backgroundColor(ColorInterface $backgroundColor): BuilderInterface;
+
+    public function logoPath(string $logoPath): BuilderInterface;
+
+    public function logoResizeToWidth(int $logoResizeToWidth): BuilderInterface;
+
+    public function logoResizeToHeight(int $logoResizeToHeight): BuilderInterface;
+
+    public function logoPunchoutBackground(bool $logoPunchoutBackground): BuilderInterface;
+
+    public function labelText(string $labelText): BuilderInterface;
+
+    public function labelFont(FontInterface $labelFont): BuilderInterface;
+
+    public function labelAlignment(LabelAlignmentInterface $labelAlignment): BuilderInterface;
+
+    public function labelMargin(MarginInterface $labelMargin): BuilderInterface;
+
+    public function labelTextColor(ColorInterface $labelTextColor): BuilderInterface;
+
+    public function validateResult(bool $validateResult): BuilderInterface;
+
+    public function build(): ResultInterface;
+}

+ 25 - 0
htdocs/includes/endroid/qr-code/src/Builder/BuilderRegistry.php

@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Builder;
+
+final class BuilderRegistry implements BuilderRegistryInterface
+{
+    /** @var array<BuilderInterface> */
+    private array $builders = [];
+
+    public function getBuilder(string $name): BuilderInterface
+    {
+        if (!isset($this->builders[$name])) {
+            throw new \Exception(sprintf('Builder with name "%s" not available from registry', $name));
+        }
+
+        return $this->builders[$name];
+    }
+
+    public function addBuilder(string $name, BuilderInterface $builder): void
+    {
+        $this->builders[$name] = $builder;
+    }
+}

+ 12 - 0
htdocs/includes/endroid/qr-code/src/Builder/BuilderRegistryInterface.php

@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Builder;
+
+interface BuilderRegistryInterface
+{
+    public function getBuilder(string $name): BuilderInterface;
+
+    public function addBuilder(string $name, BuilderInterface $builder): void;
+}

+ 56 - 0
htdocs/includes/endroid/qr-code/src/Color/Color.php

@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Color;
+
+final class Color implements ColorInterface
+{
+    public function __construct(
+        private int $red,
+        private int $green,
+        private int $blue,
+        private int $alpha = 0
+    ) {
+    }
+
+    public function getRed(): int
+    {
+        return $this->red;
+    }
+
+    public function getGreen(): int
+    {
+        return $this->green;
+    }
+
+    public function getBlue(): int
+    {
+        return $this->blue;
+    }
+
+    public function getAlpha(): int
+    {
+        return $this->alpha;
+    }
+
+    public function getOpacity(): float
+    {
+        return 1 - $this->alpha / 127;
+    }
+
+    public function getHex(): string
+    {
+        return sprintf('#%02x%02x%02x', $this->red, $this->green, $this->blue);
+    }
+
+    public function toArray(): array
+    {
+        return [
+            'red' => $this->red,
+            'green' => $this->green,
+            'blue' => $this->blue,
+            'alpha' => $this->alpha,
+        ];
+    }
+}

+ 23 - 0
htdocs/includes/endroid/qr-code/src/Color/ColorInterface.php

@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Color;
+
+interface ColorInterface
+{
+    public function getRed(): int;
+
+    public function getGreen(): int;
+
+    public function getBlue(): int;
+
+    public function getAlpha(): int;
+
+    public function getOpacity(): float;
+
+    public function getHex(): string;
+
+    /** @return array<string, int> */
+    public function toArray(): array;
+}

+ 21 - 0
htdocs/includes/endroid/qr-code/src/Encoding/Encoding.php

@@ -0,0 +1,21 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Encoding;
+
+final class Encoding implements EncodingInterface
+{
+    public function __construct(
+        private string $value
+    ) {
+        if (!in_array($value, mb_list_encodings())) {
+            throw new \Exception(sprintf('Invalid encoding "%s"', $value));
+        }
+    }
+
+    public function __toString(): string
+    {
+        return $this->value;
+    }
+}

+ 10 - 0
htdocs/includes/endroid/qr-code/src/Encoding/EncodingInterface.php

@@ -0,0 +1,10 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Endroid\QrCode\Encoding;
+
+interface EncodingInterface
+{
+    public function __toString(): string;
+}

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません