/**
* @created 11.07.2022
* @author smiley <smiley@chillerlan.net>
* @copyright 2022 smiley
* @license MIT
*/
import QROutputAbstract from './QROutputAbstract.js';
import {LAYERNAMES} from '../Common/constants.js';
/**
* SVG output
*
* @see https://github.com/codemasher/php-qrcode/pull/5
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/svg
* @see https://www.sarasoueidan.com/demos/interactive-svg-coordinate-system/
* @see https://web.archive.org/web/20200220211445/http://apex.infogridpacific.com/SVG/svg-tutorial-contents.html
*/
export default class QRMarkupSVG extends QROutputAbstract{
/**
* @inheritDoc
*/
mimeType = 'image/svg+xml';
/**
* @protected
*/
getCssClass($M_TYPE){
return [
`qr-${LAYERNAMES[$M_TYPE] ?? $M_TYPE}`,
this.matrix.isDark($M_TYPE) ? 'dark' : 'light',
this.options.cssClass,
].join(' ');
}
/**
* @inheritDoc
*
* @returns {HTMLElement|SVGElement|ChildNode|string|*}
*/
dump($file = null){
let $data = this.createMarkup($file !== null);
this.saveToFile($data, $file);
if(this.options.returnAsDomElement){
let doc = new DOMParser().parseFromString($data.trim(), this.mimeType);
return doc.firstChild;
}
if(this.options.outputBase64){
$data = this.toBase64DataURI($data, this.mimeType);
}
return $data;
}
/**
* @protected
*/
createMarkup($saveToFile){
let $svg = this.header();
let $eol = this.options.eol;
if(this.options.svgDefs){
let $s1 = this.options.svgDefs;
$svg += `<defs>${$s1}${$eol}</defs>${$eol}`;
}
$svg += this.paths();
// close svg
$svg += `${$eol}</svg>${$eol}`;
return $svg;
}
/**
* returns the value for the SVG viewBox attribute
*
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
* @see https://css-tricks.com/scale-svg/#article-header-id-3
*
* @returns {string}
* @protected
*/
getViewBox(){
let [$width, $height] = this.getOutputDimensions();
return `0 0 ${$width} ${$height}`;
}
/**
* returns the <svg> header with the given options parsed
*
* @returns {string}
* @protected
*/
header(){
let $header = `<svg xmlns="http://www.w3.org/2000/svg" class="qr-svg ${this.options.cssClass}" `
+ `viewBox="${this.getViewBox()}" preserveAspectRatio="${this.options.svgPreserveAspectRatio}">${this.options.eol}`;
if(this.options.svgAddXmlHeader){
$header = `<?xml version="1.0" encoding="UTF-8"?>${this.options.eol}${$header}`;
}
return $header;
}
/**
* returns one or more SVG <path> elements
*
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
*
* @returns {string}
* @protected
*/
paths(){
let $paths = this.collectModules(($x, $y, $M_TYPE) => this.module($x, $y, $M_TYPE));
let $svg = [];
// create the path elements
for(let $M_TYPE in $paths){
// limit the total line length
let $chunkSize = 100;
let $chonks = [];
for(let $i = 0; $i < $paths[$M_TYPE].length; $i += $chunkSize){
$chonks.push($paths[$M_TYPE].slice($i, $i + $chunkSize).join(' '));
}
let $path = $chonks.join(this.options.eol);
if($path.trim() === ''){
continue;
}
$svg.push(this.path($path, $M_TYPE));
}
return $svg.join(this.options.eol);
}
/**
* renders and returns a single <path> element
*
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
*
* @param {string} $path
* @param {number|int} $M_TYPE
* @returns {string}
* @protected
*/
path($path, $M_TYPE){
let $cssClass = this.getCssClass($M_TYPE);
if(this.options.svgUseFillAttributes){
return `<path class="${$cssClass}" fill="${this.getModuleValue($M_TYPE)}" `
+ `fill-opacity="${this.options.svgOpacity}" d="${$path}"/>`;
}
return `<path class="${$cssClass}" d="${$path}"/>`;
}
/**
* returns a path segment for a single module
*
* @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
*
* @param {number|int} $x
* @param {number|int} $y
* @param {number|int} $M_TYPE
*
* @returns {string}
* @protected
*/
module($x, $y, $M_TYPE){
if(!this.options.drawLightModules && !this.matrix.check($x, $y)){
return '';
}
if(this.options.drawCircularModules && !this.matrix.checkTypeIn($x, $y, this.options.keepAsSquare)){
// some values come with the usual JS float fun and i won't do shit about it
let r = parseFloat(this.options.circleRadius);
let d = (r * 2);
let ix = ($x + 0.5 - r);
let iy = ($y + 0.5);
if(ix < 1){
ix = ix.toPrecision(3);
}
if(iy < 1){
iy = iy.toPrecision(3);
}
return `M${ix} ${iy} a${r} ${r} 0 1 0 ${d} 0 a${r} ${r} 0 1 0 -${d} 0Z`;
}
return `M${$x} ${$y} h1 v1 h-1Z`;
}
}