QRMarkup.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. /**
  3. * Class QRMarkup
  4. *
  5. * @created 17.12.2016
  6. * @author Smiley <smiley@chillerlan.net>
  7. * @copyright 2016 Smiley
  8. * @license MIT
  9. */
  10. namespace chillerlan\QRCode\Output;
  11. use chillerlan\QRCode\Data\QRMatrix;
  12. use chillerlan\QRCode\QRCode;
  13. use function implode, is_string, ksort, sprintf, strip_tags, trim;
  14. /**
  15. * Converts the matrix into markup types: HTML, SVG, ...
  16. */
  17. class QRMarkup extends QROutputAbstract{
  18. protected string $defaultMode = QRCode::OUTPUT_MARKUP_SVG;
  19. /**
  20. * @inheritDoc
  21. */
  22. protected function setModuleValues():void{
  23. foreach($this::DEFAULT_MODULE_VALUES as $M_TYPE => $defaultValue){
  24. $v = $this->options->moduleValues[$M_TYPE] ?? null;
  25. if(!is_string($v)){
  26. $this->moduleValues[$M_TYPE] = $defaultValue
  27. ? $this->options->markupDark
  28. : $this->options->markupLight;
  29. }
  30. else{
  31. $this->moduleValues[$M_TYPE] = trim(strip_tags($v), " '\"\r\n\t");
  32. }
  33. }
  34. }
  35. /**
  36. * HTML output
  37. */
  38. protected function html(string $file = null):string{
  39. $html = empty($this->options->cssClass)
  40. ? '<div>'
  41. : sprintf('<div class="%s">', $this->options->cssClass);
  42. $html .= $this->options->eol;
  43. foreach($this->matrix->matrix() as $row){
  44. $html .= '<div>';
  45. foreach($row as $M_TYPE){
  46. $html .= sprintf('<span style="background: %s;"></span>', $this->moduleValues[$M_TYPE]);
  47. }
  48. $html .= '</div>'.$this->options->eol;
  49. }
  50. $html .= '</div>'.$this->options->eol;
  51. if($file !== null){
  52. return sprintf(
  53. '<!DOCTYPE html><head><meta charset="UTF-8"><title>QR Code</title></head><body>%s</body>',
  54. $this->options->eol.$html
  55. );
  56. }
  57. return $html;
  58. }
  59. /**
  60. * SVG output
  61. *
  62. * @see https://github.com/codemasher/php-qrcode/pull/5
  63. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
  64. * @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
  65. */
  66. protected function svg(string $file = null):string{
  67. $svg = $this->svgHeader();
  68. if(!empty($this->options->svgDefs)){
  69. $svg .= sprintf('<defs>%1$s%2$s</defs>%2$s', $this->options->svgDefs, $this->options->eol);
  70. }
  71. $svg .= $this->svgPaths();
  72. // close svg
  73. $svg .= sprintf('%1$s</svg>%1$s', $this->options->eol);
  74. // transform to data URI only when not saving to file
  75. if($file === null && $this->options->imageBase64){
  76. $svg = $this->base64encode($svg, 'image/svg+xml');
  77. }
  78. return $svg;
  79. }
  80. /**
  81. * returns the <svg> header with the given options parsed
  82. */
  83. protected function svgHeader():string{
  84. $width = $this->options->svgWidth !== null ? sprintf(' width="%s"', $this->options->svgWidth) : '';
  85. $height = $this->options->svgHeight !== null ? sprintf(' height="%s"', $this->options->svgHeight) : '';
  86. /** @noinspection HtmlUnknownAttribute */
  87. return sprintf(
  88. '<?xml version="1.0" encoding="UTF-8"?>%6$s'.
  89. '<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg %1$s" viewBox="0 0 %2$s %2$s" preserveAspectRatio="%3$s"%4$s%5$s>%6$s',
  90. $this->options->cssClass,
  91. $this->options->svgViewBoxSize ?? $this->moduleCount,
  92. $this->options->svgPreserveAspectRatio,
  93. $width,
  94. $height,
  95. $this->options->eol
  96. );
  97. }
  98. /**
  99. * returns one or more SVG <path> elements
  100. *
  101. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
  102. */
  103. protected function svgPaths():string{
  104. $paths = [];
  105. // collect the modules for each type
  106. foreach($this->matrix->matrix() as $y => $row){
  107. foreach($row as $x => $M_TYPE){
  108. if($this->options->svgConnectPaths && !$this->matrix->checkTypes($x, $y, $this->options->svgExcludeFromConnect)){
  109. // to connect paths we'll redeclare the $M_TYPE to data only
  110. $M_TYPE = QRMatrix::M_DATA;
  111. if($this->matrix->check($x, $y)){
  112. $M_TYPE |= QRMatrix::IS_DARK;
  113. }
  114. }
  115. // collect the modules per $M_TYPE
  116. $paths[$M_TYPE][] = $this->svgModule($x, $y);
  117. }
  118. }
  119. // beautify output
  120. ksort($paths);
  121. $svg = [];
  122. // create the path elements
  123. foreach($paths as $M_TYPE => $path){
  124. $path = trim(implode(' ', $path));
  125. if(empty($path)){
  126. continue;
  127. }
  128. $cssClass = implode(' ', [
  129. 'qr-'.$M_TYPE,
  130. ($M_TYPE & QRMatrix::IS_DARK) === QRMatrix::IS_DARK ? 'dark' : 'light',
  131. $this->options->cssClass,
  132. ]);
  133. $format = empty($this->moduleValues[$M_TYPE])
  134. ? '<path class="%1$s" d="%2$s"/>'
  135. : '<path class="%1$s" fill="%3$s" fill-opacity="%4$s" d="%2$s"/>';
  136. $svg[] = sprintf($format, $cssClass, $path, $this->moduleValues[$M_TYPE], $this->options->svgOpacity);
  137. }
  138. return implode($this->options->eol, $svg);
  139. }
  140. /**
  141. * returns a path segment for a single module
  142. *
  143. * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
  144. */
  145. protected function svgModule(int $x, int $y):string{
  146. if($this->options->imageTransparent && !$this->matrix->check($x, $y)){
  147. return '';
  148. }
  149. if($this->options->svgDrawCircularModules && !$this->matrix->checkTypes($x, $y, $this->options->svgKeepAsSquare)){
  150. $r = $this->options->svgCircleRadius;
  151. return sprintf(
  152. 'M%1$s %2$s a%3$s %3$s 0 1 0 %4$s 0 a%3$s,%3$s 0 1 0 -%4$s 0Z',
  153. ($x + 0.5 - $r),
  154. ($y + 0.5),
  155. $r,
  156. ($r * 2)
  157. );
  158. }
  159. return sprintf('M%1$s %2$s h%3$s v1 h-%4$sZ', $x, $y, 1, 1);
  160. }
  161. }