/**
* @created 11.07.2022
* @author smiley <smiley@chillerlan.net>
* @copyright 2022 smiley
* @license MIT
*/
import QROptions from './QROptions.js';
import MaskPattern from './Common/MaskPattern.js';
import AlphaNum from './Data/AlphaNum.js';
import Byte from './Data/Byte.js';
import Numeric from './Data/Numeric.js';
import QRData from './Data/QRData.js';
import QRCodeOutputException from './Output/QRCodeOutputException.js';
import QROutputInterface from './Output/QROutputInterface.js';
import PHPJS from './Common/PHPJS.js';
import {
MASK_PATTERN_AUTO, MODE_ALPHANUM, MODE_BYTE, MODE_NUMBER
} from './Common/constants.js';
/**
* Map of data mode => interface (detection order)
*
* @type {string[]}
*/
const MODE_INTERFACES = PHPJS.array_combine([MODE_NUMBER, MODE_ALPHANUM, MODE_BYTE], [Numeric, AlphaNum, Byte]);
/**
* Turns a text string into a Model 2 QR Code
*
* @see https://github.com/chillerlan/php-qrcode
* @see https://github.com/kazuhikoarase/qrcode-generator/tree/master/php
* @see http://www.qrcode.com/en/codes/model12.html
* @see https://www.swisseduc.ch/informatik/theoretische_informatik/qr_codes/docs/qr_standard.pdf
* @see https://en.wikipedia.org/wiki/QR_code
* @see http://www.thonky.com/qr-code-tutorial/
* @see https://jamie.build/const
*/
export default class QRCode{
/**
* @type {QROptions}
* @protected
*/
options;
/**
* @type {Array}
* @protected
*/
dataSegments = [];
/**
* QRCode constructor.
*
* @param {QROptions} $options
*/
constructor($options){
this.setOptions($options);
}
/**
* Sets an options instance
*
* @param {QROptions} $options
*
* @returns {QRCode}
*/
setOptions($options){
if(!($options instanceof QROptions)){
$options = new QROptions();
}
this.options = $options;
return this;
}
/**
* Renders a QR Code for the given $data and QROptions
*
* @returns {*}
*/
render($data = null, $file = null){
if($data !== null){
for(let $mode in MODE_INTERFACES){
let $dataInterface = MODE_INTERFACES[$mode];
if($dataInterface.validateString($data)){
this.addSegment(new $dataInterface($data));
break;
}
}
}
return this.renderMatrix(this.getQRMatrix(), $file);
}
/**
* Renders a QR Code for the given QRMatrix and QROptions, saves $file optionally
*
* @param {QRMatrix} $QRMatrix
* @param {string|null} $file
* @returns {*}
*/
renderMatrix($QRMatrix, $file = null){
return this.initOutputInterface($QRMatrix).dump($file ?? this.options.cachefile);
}
/**
* Returns a QRMatrix object for the given $data and current QROptions
*
* @returns {QRMatrix}
* @throws {QRCodeDataException}
*/
getQRMatrix(){
let $QRMatrix = new QRData(this.options, this.dataSegments).writeMatrix();
let $maskPattern = this.options.maskPattern === MASK_PATTERN_AUTO
? MaskPattern.getBestPattern($QRMatrix)
: new MaskPattern(this.options.maskPattern);
$QRMatrix.setFormatInfo($maskPattern).mask($maskPattern);
return this.addMatrixModifications($QRMatrix);
}
/**
* add matrix modifications after mask pattern evaluation and before handing over to output
*
* @param {QRMatrix} $QRMatrix
* @returns {QRMatrix}
* @protected
*/
addMatrixModifications($QRMatrix){
if(this.options.addLogoSpace){
// check whether one of the dimensions was omitted
let $logoSpaceWidth = (this.options.logoSpaceWidth ?? this.options.logoSpaceHeight ?? 0);
let $logoSpaceHeight = (this.options.logoSpaceHeight ?? $logoSpaceWidth);
$QRMatrix.setLogoSpace(
$logoSpaceWidth,
$logoSpaceHeight,
this.options.logoSpaceStartX,
this.options.logoSpaceStartY
);
}
if(this.options.addQuietzone){
$QRMatrix.setQuietZone(this.options.quietzoneSize);
}
return $QRMatrix;
}
/**
* initializes a fresh built-in or custom QROutputInterface
*
* @param {QRMatrix} $QRMatrix
* @returns {QROutputAbstract}
* @throws {QRCodeOutputException}
* @protected
*/
initOutputInterface($QRMatrix){
if(typeof this.options.outputInterface !== 'function'){
throw new QRCodeOutputException('invalid output class');
}
let $outputInterface = new this.options.outputInterface(this.options, $QRMatrix);
if(!($outputInterface instanceof QROutputInterface)){
throw new QRCodeOutputException('output class does not implement QROutputInterface');
}
return $outputInterface
}
/**
* Adds a data segment
*
* ISO/IEC 18004:2000 8.3.6 - Mixing modes
* ISO/IEC 18004:2000 Annex H - Optimisation of bit stream length
*
* @returns {QRCode}
*/
addSegment($segment){
this.dataSegments.push($segment);
return this;
}
/**
* Clears the data segments array
*
* @returns {QRCode}
*/
clearSegments(){
this.dataSegments = [];
return this;
}
/**
* Adds a numeric data segment
*
* ISO/IEC 18004:2000 8.3.2 - Numeric Mode
*
* @returns {QRCode}
*/
addNumericSegment($data){
this.addSegment(new Numeric($data));
return this;
}
/**
* Adds an alphanumeric data segment
*
* ISO/IEC 18004:2000 8.3.3 - Alphanumeric Mode
*
* @returns {QRCode}
*/
addAlphaNumSegment($data){
this.addSegment(new AlphaNum($data));
return this;
}
/**
* Adds an 8-bit byte data segment
*
* ISO/IEC 18004:2000 8.3.4 - 8-bit Byte Mode
*
* @returns {QRCode}
*/
addByteSegment($data){
this.addSegment(new Byte($data));
return this;
}
}