import { bcadd, bcdiv, bcmul } from "./bcmath"; import { textsubmodes, textlatch, clusters, rsfactors, TextLatchKeys } from "./pdf417LookupTables"; /** * PDF417 - 2D Barcode generator (LGPLv3) * * Ported from PHP - PDF417 class, version 1.0.005, from TCPDF library (http://www.tcpdf.org/) */ export interface BarcodeArray { num_rows: number; num_cols: number; bcode: number[][]; } const ROWHEIGHT = 4; const QUIETH = 2; const QUIETV = 2; const start_pattern = "11111111010101000"; const stop_pattern = "111111101000101001"; function getInputSequences(code: string): [number, string][] { var sequence_array: [number, string][] = []; // array to be returned var numseq: [string, number][] = []; // get numeric sequences var numseqMatch = code.match(/([0-9]{13,44})/g); if (numseqMatch == null) { numseq = []; } else { // add offset to each matched line for (var n = 0, offset = 0; n < numseqMatch.length; n++) { offset = code.indexOf(numseqMatch[n], offset); numseq.push([numseqMatch[n], offset]); offset += numseqMatch[n].length; } } numseq.push(["", code.length]); var offset = 0; for (var i = 0; i < numseq.length; i++) { var seq = numseq[i]; var seqlen = seq[0].length; if (seq[1] > 0) { // extract text sequence before the number sequence var prevseq = code.substr(offset, seq[1] - offset); var textseq: [string, number][] = []; // get text sequences var textseqMatch = prevseq.match(/([\x09\x0a\x0d\x20-\x7e]{5,})/g); if (textseqMatch == null) { textseq = []; } else { // add offset to each matched line for (var n = 0; n < textseqMatch.length; n++) { var txtOffset = prevseq.indexOf(textseqMatch[n]); textseq.push([textseqMatch[n], txtOffset]); } } textseq.push(["", prevseq.length]); var txtoffset = 0; for (var j = 0; j < textseq.length; j++) { var txtseq = textseq[j]; var txtseqlen = txtseq[0].length; if (txtseq[1] > 0) { // extract byte sequence before the text sequence var prevtxtseq = prevseq.substr(txtoffset, txtseq[1] - txtoffset); if (prevtxtseq.length > 0) { // add BYTE sequence if ( prevtxtseq.length == 1 && sequence_array.length > 0 && sequence_array[sequence_array.length - 1][0] == 900 ) { sequence_array.push([913, prevtxtseq]); } else if (prevtxtseq.length % 6 == 0) { sequence_array.push([924, prevtxtseq]); } else { sequence_array.push([901, prevtxtseq]); } } } if (txtseqlen > 0) { // add numeric sequence sequence_array.push([900, txtseq[0]]); } txtoffset = txtseq[1] + txtseqlen; } } if (seqlen > 0) { // add numeric sequence sequence_array.push([902, seq[0]]); } offset = seq[1] + seqlen; } return sequence_array; }; function getCompaction(mode: number, code: string, addmode?: boolean): number[] { addmode = addmode ?? true; var cw: number[] = []; // array of codewords to return switch (mode) { case 900: { // Text Compaction mode latch var submode = 0; // default Alpha sub-mode var txtarr: number[] = []; // array of characters and sub-mode switching characters var codelen = code.length; for (var i = 0; i < codelen; ++i) { var chval = _ord(code.charAt(i)); var k: number | string | false; if ( (k = _array_search(chval, textsubmodes[submode])) !== false ) { // we are on the same sub-mode txtarr.push(k as number); } else { // the sub-mode is changed for (var s = 0; s < 4; ++s) { // search new sub-mode if ( s != submode && (k = _array_search(chval, textsubmodes[s])) !== false ) { // s is the new submode if ( (i + 1 == codelen || (i + 1 < codelen && _array_search( _ord(code.charAt(i + 1)), textsubmodes[submode] ) !== false)) && (s == 3 || (s == 0 && submode == 1)) ) { // shift (temporary change only for this char) if (s == 3) { // shift to puntuaction txtarr.push(29); } else { // shift from lower to alpha txtarr.push(27); } } else { // latch txtarr = txtarr.concat(textlatch[("" + submode + s) as TextLatchKeys]); // set new submode submode = s; } // add characted code to array txtarr.push(k as number); break; } } } } var txtarrlen = txtarr.length; if (txtarrlen % 2 != 0) { // add padding txtarr.push(29); ++txtarrlen; } // calculate codewords for (var i = 0; i < txtarrlen; i += 2) { cw.push(30 * parseInt(String(txtarr[i])) + parseInt(String(txtarr[i + 1]))); } break; } case 901: case 924: { // Byte Compaction mode latch var rest: string; var sublen: number; var codelen: number; while ((codelen = code.length) > 0) { if (codelen > 6) { rest = code.substring(6); code = code.substring(0, 6); sublen = 6; } else { rest = ""; sublen = code.length; } if (sublen == 6) { var t = bcmul("" + _ord(code.charAt(0)), "1099511627776"); t = bcadd(t, bcmul("" + _ord(code.charAt(1)), "4294967296")); t = bcadd(t, bcmul("" + _ord(code.charAt(2)), "16777216")); t = bcadd(t, bcmul("" + _ord(code.charAt(3)), "65536")); t = bcadd(t, bcmul("" + _ord(code.charAt(4)), "256")); t = bcadd(t, "" + _ord(code.charAt(5))); // tmp array for the 6 bytes block var cw6: number[] = []; do { var d = _my_bcmod(t, "900"); t = bcdiv(t, "900"); // prepend the value to the beginning of the array cw6.unshift(d); } while (t != "0"); // append the result array at the end cw = cw.concat(cw6); } else { for (var i = 0; i < sublen; ++i) { cw.push(_ord(code.charAt(i))); } } code = rest; } break; } case 902: { // Numeric Compaction mode latch var rest: string; var codelen: number; while ((codelen = code.length) > 0) { if (codelen > 44) { rest = code.substring(44); code = code.substring(0, 44); } else { rest = ""; } var t = "1" + code; do { var d = _my_bcmod(t, "900"); t = bcdiv(t, "900"); cw.unshift(d); } while (t != "0"); code = rest; } break; } case 913: { // Byte Compaction mode shift cw.push(_ord(code)); break; } } if (addmode) { // add the compaction mode codeword at the beginning cw.unshift(mode); } return cw; }; function getErrorCorrectionLevel(ecl: number, numcw: number): number { // get maximum correction level var maxecl = 8; // starting error level var maxerrsize = 928 - numcw; // available codewords for error while (maxecl > 0) { var errsize = 2 << ecl; if (maxerrsize >= errsize) { break; } --maxecl; } // check for automatic levels if (ecl < 0 || ecl > 8) { if (numcw < 41) { ecl = 2; } else if (numcw < 161) { ecl = 3; } else if (numcw < 321) { ecl = 4; } else if (numcw < 864) { ecl = 5; } else { ecl = maxecl; } } if (ecl > maxecl) { ecl = maxecl; } return ecl; }; function getErrorCorrection(cw: number[], ecl: number): number[] { // get error correction coefficients var ecc = rsfactors[ecl]; // number of error correction factors var eclsize = 2 << ecl; // maximum index for rsfactors[ecl] var eclmaxid = eclsize - 1; // initialize array of error correction codewords var ecw = _array_fill(0, eclsize, 0) as number[]; // for each data codeword for (var k = 0; k < cw.length; k++) { var t1 = (cw[k] + ecw[eclmaxid]) % 929; for (var j = eclmaxid; j > 0; --j) { var t2 = t1 * ecc[j] % 929; var t3 = 929 - t2; ecw[j] = (ecw[j - 1] + t3) % 929; } t2 = t1 * ecc[0] % 929; t3 = 929 - t2; ecw[0] = t3 % 929; } for (var j = 0; j < ecw.length; j++) { if (ecw[j] != 0) { ecw[j] = 929 - ecw[j]; } } ecw = ecw.reverse(); return ecw; }; /** * * Functions from phpjs.org * */ function _array_fill(start_index: number, num: number, mixed_val: number): number[] | Record { var key: number, tmp_arr: Record = {}; if (start_index == 0) { var tmpArray: number[] = []; for (var i = 0; i < num; i++) { tmpArray.push(mixed_val); } return tmpArray; } if (!isNaN(start_index) && !isNaN(num)) { for (key = 0; key < num; key++) { tmp_arr[key + start_index] = mixed_val; } } return tmp_arr; }; function _str_repeat(input: string, multiplier: number): string { // http://kevin.vanzonneveld.net // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + improved by: Jonas Raoni Soares Silva (http://www.jsfromhell.com) // + improved by: Ian Carter (http://euona.com/) // * example 1: str_repeat('-=', 10); // * returns 1: '-=-=-=-=-=-=-=-=-=-=' var y = ""; while (true) { if (multiplier & 1) { y += input; } multiplier >>= 1; if (multiplier) { input += input; } else { break; } } return y; }; function _intval(mixed_var: any, base?: number): number { // http://kevin.vanzonneveld.net // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + improved by: stensi // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + input by: Matteo // + bugfixed by: Brett Zamir (http://brett-zamir.me) // + bugfixed by: Rafał Kukawski (http://kukawski.pl) // * example 1: intval('Kevin van Zonneveld'); // * returns 1: 0 // * example 2: intval(4.2); // * returns 2: 4 // * example 3: intval(42, 8); // * returns 3: 42 // * example 4: intval('09'); // * returns 4: 9 // * example 5: intval('1e', 16); // * returns 5: 30 var tmp: number; var type = typeof mixed_var; if (type === "boolean") { return +mixed_var; } else if (type === "string") { tmp = parseInt(mixed_var, base || 10); return isNaN(tmp) || !isFinite(tmp) ? 0 : tmp; } else if (type === "number" && isFinite(mixed_var)) { return mixed_var | 0; } else { return 0; } }; function _sprintf(format: string, ...args: any[]): string { // http://kevin.vanzonneveld.net // + original by: Ash Searle (http://hexmen.com/blog/) // + namespaced by: Michael White (http://getsprink.com) // + tweaked by: Jack // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + input by: Paulo Freitas // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + input by: Brett Zamir (http://brett-zamir.me) // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + improved by: Dj // + improved by: Allidylls // * example 1: sprintf("%01.2f", 123.1); // * returns 1: 123.10 // * example 2: sprintf("[%10s]", 'monkey'); // * returns 2: '[ monkey]' // * example 3: sprintf("[%'#10s]", 'monkey'); // * returns 3: '[####monkey]' // * example 4: sprintf("%d", 123456789012345); // * returns 4: '123456789012345' var regex = /%%|%(\d+\$)?([-+\'#0 ]*)(\*\d+\$|\*|\d+)?(\.(\*\d+\$|\*|\d+))?([scboxXuideEfFgG])/g; var a = [format, ...args], i = 0; format = a[i++]; // pad() var pad = function (str: string, len: number, chr?: string, leftJustify?: boolean): string { if (!chr) { chr = " "; } var padding = str.length >= len ? "" : Array((1 + len - str.length) >>> 0).join(chr); return leftJustify ? str + padding : padding + str; }; // justify() var justify = function ( value: string, prefix: string, leftJustify: boolean, minWidth: number, zeroPad: boolean, customPadChar?: string ): string { var diff = minWidth - value.length; if (diff > 0) { if (leftJustify || !zeroPad) { value = pad(value, minWidth, customPadChar, leftJustify); } else { value = value.slice(0, prefix.length) + pad("", diff, "0", true) + value.slice(prefix.length); } } return value; }; // formatBaseX() var formatBaseX = function ( value: number, base: number, prefix: string | boolean, leftJustify: boolean, minWidth: number, precision: number, zeroPad: boolean ): string { // Note: casts negative numbers to positive ones var number = value >>> 0; var prefixStr = (prefix && number && ({ "2": "0b", "8": "0", "16": "0x" } as Record)[String(base)]) || ""; value = number; var valueStr = prefixStr + pad(number.toString(base), precision || 0, "0", false); return justify(valueStr, prefixStr, leftJustify, minWidth, zeroPad); }; // formatString() var formatString = function ( value: string, leftJustify: boolean, minWidth: number, precision: number | null, zeroPad: boolean, customPadChar?: string ): string { if (precision != null) { value = value.slice(0, precision); } return justify( value, "", leftJustify, minWidth, zeroPad, customPadChar ); }; // doFormat() var doFormat = function ( substring: string, valueIndex: string | undefined, flags: string, minWidth: string, _: any, precision: string, type: string ): string { var number: number; var prefix: string; var method: string; var textTransform: string; var value: any; if (substring == "%%") { return "%"; } // parse flags var leftJustify = false, positivePrefix = "", zeroPad = false, prefixBaseX: string | boolean = false, customPadChar = " "; var flagsl = flags.length; for (var j = 0; flags && j < flagsl; j++) { switch (flags.charAt(j)) { case " ": positivePrefix = " "; break; case "+": positivePrefix = "+"; break; case "-": leftJustify = true; break; case "'": customPadChar = flags.charAt(j + 1); break; case "0": zeroPad = true; break; case "#": prefixBaseX = true; break; } } // parameters may be null, undefined, empty-string or real valued // we want to ignore null, undefined and empty-string values var minWidthNum = 0; if (!minWidth) { minWidthNum = 0; } else if (minWidth == "*") { minWidthNum = +a[i++]; } else if (minWidth.charAt(0) == "*") { minWidthNum = +a[parseInt(minWidth.slice(1, -1))]; } else { minWidthNum = +minWidth; } // Note: undocumented perl feature: if (minWidthNum < 0) { minWidthNum = -minWidthNum; leftJustify = true; } if (!isFinite(minWidthNum)) { throw new Error("sprintf: (minimum-)width must be finite"); } var precisionNum: number | undefined; if (!precision) { precisionNum = "fFeE".indexOf(type) > -1 ? 6 : type == "d" ? 0 : undefined; } else if (precision == "*") { precisionNum = +a[i++]; } else if (precision.charAt(0) == "*") { precisionNum = +a[parseInt(precision.slice(1, -1))]; } else { precisionNum = +precision; } // grab value using valueIndex if required? value = valueIndex ? a[parseInt(valueIndex.slice(0, -1))] : a[i++]; switch (type) { case "s": return formatString( String(value), leftJustify, minWidthNum, precisionNum === undefined ? null : precisionNum, zeroPad, customPadChar ); case "c": return formatString( String.fromCharCode(+value), leftJustify, minWidthNum, precisionNum === undefined ? null : precisionNum, zeroPad ); case "b": return formatBaseX( value, 2, prefixBaseX, leftJustify, minWidthNum, precisionNum || 0, zeroPad ); case "o": return formatBaseX( value, 8, prefixBaseX, leftJustify, minWidthNum, precisionNum || 0, zeroPad ); case "x": return formatBaseX( value, 16, prefixBaseX, leftJustify, minWidthNum, precisionNum || 0, zeroPad ); case "X": return formatBaseX( value, 16, prefixBaseX, leftJustify, minWidthNum, precisionNum || 0, zeroPad ).toUpperCase(); case "u": return formatBaseX( value, 10, prefixBaseX, leftJustify, minWidthNum, precisionNum || 0, zeroPad ); case "i": case "d": number = +value || 0; number = Math.round(number - number % 1); // Plain Math.round doesn't just truncate prefix = number < 0 ? "-" : positivePrefix; value = prefix + pad(String(Math.abs(number)), precisionNum || 0, "0", false); return justify(value, prefix, leftJustify, minWidthNum, zeroPad); case "e": case "E": case "f": // Should handle locales (as per setlocale) case "F": case "g": case "G": number = +value; prefix = number < 0 ? "-" : positivePrefix; method = ["toExponential", "toFixed", "toPrecision"][ "efg".indexOf(type.toLowerCase()) ]; textTransform = ["toString", "toUpperCase"][ "eEfFgG".indexOf(type) % 2 ]; value = prefix + Math.abs(number)[method as 'toExponential' | 'toFixed' | 'toPrecision'](precisionNum); return justify(value, prefix, leftJustify, minWidthNum, zeroPad)[ textTransform as 'toString' | 'toUpperCase' ](); default: return substring; } }; return format.replace(regex, doFormat as any); }; // function _preg_split(pattern: RegExp | string, subject: string, limit?: number, flags?: string | number): (string | [string, number])[] { // // http://kevin.vanzonneveld.net // // + original by: Marco Marchiò // // * example 1: preg_split(/[\s,]+/, 'hypertext language, programming'); // // * returns 1: ['hypertext', 'language', 'programming'] // // * example 2: preg_split('//', 'string', -1, 'PREG_SPLIT_NO_EMPTY'); // // * returns 2: ['s', 't', 'r', 'i', 'n', 'g'] // // * example 3: var str = 'hypertext language programming'; // // * example 3: preg_split('/ /', str, -1, 'PREG_SPLIT_OFFSET_CAPTURE'); // // * returns 3: [['hypertext', 0], ['language', 10], ['programming', 19]] // // * example 4: preg_split('/( )/', '1 2 3 4 5 6 7 8', 4, 'PREG_SPLIT_DELIM_CAPTURE'); // // * returns 4: ['1', ' ', '2', ' ', '3', ' ', '4 5 6 7 8'] // // * example 5: preg_split('/( )/', '1 2 3 4 5 6 7 8', 4, (2 | 4)); // // * returns 5: [['1', 0], [' ', 1], ['2', 2], [' ', 3], ['3', 4], [' ', 5], ['4 5 6 7 8', 6]] // limit = limit || 0; // flags = flags || ""; // Limit and flags are optional // var result: RegExpExecArray | null, // ret: (string | [string, number])[] = [], // index = 0, // i = 0, // noEmpty = false, // delim = false, // offset = false, // OPTS: Record = {}, // optTemp = 0; // var patternRegex: RegExp; // if (typeof pattern === 'string') { // var regexpBody = /^\/(.*)\/\w*$/.exec(pattern)?.[1] || pattern; // var regexpFlags = /^\/.*\/(\w*)$/.exec(pattern)?.[1] || ''; // patternRegex = new RegExp(regexpBody, regexpFlags + (regexpFlags.indexOf("g") !== -1 ? "" : "g")); // } else { // var regexpBody = pattern.source; // var regexpFlags = pattern.flags || ''; // // Non-global regexp causes an infinite loop when executing the while, // // so if it's not global, copy the regexp and add the "g" modifier. // patternRegex = pattern.global // ? pattern // : new RegExp( // regexpBody, // regexpFlags + (regexpFlags.indexOf("g") !== -1 ? "" : "g") // ); // } // OPTS = { // PREG_SPLIT_NO_EMPTY: 1, // PREG_SPLIT_DELIM_CAPTURE: 2, // PREG_SPLIT_OFFSET_CAPTURE: 4 // }; // if (typeof flags !== "number") { // // Allow for a single string or an array of string flags // var flagsArr = [].concat(flags as any); // for (i = 0; i < flagsArr.length; i++) { // // Resolve string input to bitwise e.g. 'PREG_SPLIT_OFFSET_CAPTURE' becomes 4 // if (OPTS[flagsArr[i]]) { // optTemp = optTemp | OPTS[flagsArr[i]]; // } // } // flags = optTemp; // } // noEmpty = !!((flags as number) & OPTS.PREG_SPLIT_NO_EMPTY); // delim = !!((flags as number) & OPTS.PREG_SPLIT_DELIM_CAPTURE); // offset = !!((flags as number) & OPTS.PREG_SPLIT_OFFSET_CAPTURE); // var _filter = function (str: string, strindex: number): void { // // If the match is empty and the PREG_SPLIT_NO_EMPTY flag is set don't add it // if (noEmpty && !str.length) { // return; // } // // If the PREG_SPLIT_OFFSET_CAPTURE flag is set // // transform the match into an array and add the index at position 1 // if (offset) { // ret.push([str, strindex]); // } else { // ret.push(str); // } // }; // // Special case for empty regexp // if (!regexpBody) { // var resultSplit = subject.split(""); // for (i = 0; i < resultSplit.length; i++) { // _filter(resultSplit[i], i); // } // return ret; // } // // Exec the pattern and get the result // while ((result = patternRegex.exec(subject))) { // // Stop if the limit is 1 // if (limit === 1) { // break; // } // // Take the correct portion of the string and filter the match // _filter(subject.slice(index, result.index), index); // index = result.index + result[0].length; // // If the PREG_SPLIT_DELIM_CAPTURE flag is set, every capture match must be included in the results array // if (delim) { // // Convert the regexp result into a normal array // var resarr = Array.prototype.slice.call(result); // for (i = 1; i < resarr.length; i++) { // if (result[i] !== undefined) { // _filter(result[i], result.index + result[0].indexOf(result[i])); // } // } // } // limit--; // } // // Filter last match // _filter(subject.slice(index, subject.length), index); // return ret; // }; function _ord(string: string): number { return string.charCodeAt(0); }; function _array_search(needle: any, haystack: any, argStrict?: boolean): string | number | false { // http://kevin.vanzonneveld.net // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // + input by: Brett Zamir (http://brett-zamir.me) // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) // * example 1: array_search('zonneveld', {firstname: 'kevin', middle: 'van', surname: 'zonneveld'}); // * returns 1: 'surname' // * example 2: ini_set('phpjs.return_phpjs_arrays', 'on'); // * example 2: var ordered_arr = array({3:'value'}, {2:'value'}, {'a':'value'}, {'b':'value'}); // * example 2: var key = array_search(/val/g, ordered_arr); // or var key = ordered_arr.search(/val/g); // * returns 2: '3' var strict = !!argStrict, key: string | number = ""; if ( haystack && typeof haystack === "object" && (haystack as any).change_key_case ) { // Duck-type check for our own array()-created PHPJS_Array return (haystack as any).search(needle, argStrict); } if (typeof needle === "object" && (needle as RegExp).exec) { // Duck-type for RegExp if (!strict) { // Let's consider case sensitive searches as strict var needleRegex = needle as RegExp; var flags = "i" + (needleRegex.global ? "g" : "") + (needleRegex.multiline ? "m" : "") + ((needleRegex as any).sticky ? "y" : ""); // sticky is FF only needle = new RegExp(needleRegex.source, flags); } for (key in haystack) { if ((needle as RegExp).test(haystack[key])) { return key; } } return false; } for (key in haystack) { if ( (strict && haystack[key] === needle) || (!strict && haystack[key] == needle) ) { return key; } } return false; }; function _my_bcmod(x: string, y: string): number { // how many numbers to take at once? carefull not to exceed (int) var take = 5; var mod = ""; do { var a = parseInt(mod + "" + x.substring(0, take)); x = x.substring(take); mod = String(a % parseInt(y)); } while (x.length); return parseInt(mod); } /** * Creates a PDF417 object * @param code (string) code to represent using PDF417 * @param ecl (int) error correction level (0-8); default -1 = automatic correction level * @param aspectratio (float) the width to height of the symbol (excluding quiet zones) */ export function generateBarcode(code: string, ecl?: number, aspectratio?: number) { code = unescape(encodeURIComponent(code)); // covert UTF-8 to ISO-8859-1 ecl = ecl || -1; aspectratio = aspectratio || 2; if (code === "") { throw new Error("PDF417 init: empty input code"); } // get the input sequence array let sequence = getInputSequences(code); let codewords: number[] = []; // array of code-words for (var i = 0; i < sequence.length; i++) { var cw = getCompaction(sequence[i][0], sequence[i][1], true); codewords = codewords.concat(cw); } if (codewords[0] == 900) { // Text Alpha is the default mode, so remove the first code codewords.shift(); } // count number of codewords var numcw = codewords.length; if (numcw > 925) { // reached maximum data codeword capacity throw new Error("PDF417 init: maximum data codeword capacity exceeded"); } // set error correction level ecl = getErrorCorrectionLevel(ecl, numcw); // number of codewords for error correction var errsize = 2 << ecl; // calculate number of columns (number of codewords per row) and rows var nce = numcw + errsize + 1; var cols = Math.round( (Math.sqrt(4761 + 68 * aspectratio * ROWHEIGHT * nce) - 69) / 34 ); // adjust cols if (cols < 1) { cols = 1; } else if (cols > 30) { cols = 30; } var rows = Math.ceil(nce / cols); var size = cols * rows; // adjust rows if (rows < 3 || rows > 90) { if (rows < 3) { rows = 3; } else if (rows > 90) { rows = 90; } cols = Math.ceil(size / rows); size = cols * rows; } if (size > 928) { // set dimensions to get maximum capacity if ( Math.abs(aspectratio - 17 * 29 / 32) < Math.abs(aspectratio - 17 * 16 / 58) ) { cols = 29; rows = 32; } else { cols = 16; rows = 58; } size = 928; } // calculate padding var pad = size - nce; if (pad > 0) { if (size - rows == nce) { --rows; size -= rows; } else { // add pading codewords = codewords.concat(_array_fill(0, pad, 900) as number[]); } } // Symbol Length Descriptor (number of data codewords including Symbol Length Descriptor and pad codewords) var sld = size - errsize; // add symbol length description codewords.unshift(sld); // calculate error correction var ecw = getErrorCorrection(codewords, ecl); // add error correction codewords codewords = codewords.concat(ecw); // add horizontal quiet zones to start and stop patterns var pstart = _str_repeat("0", QUIETH) + start_pattern; var pstop = stop_pattern + "" + _str_repeat("0", QUIETH); const barcode_array: BarcodeArray = { num_rows: rows * ROWHEIGHT + 2 * QUIETV, num_cols: (cols + 2) * 17 + 35 + 2 * QUIETH, bcode: [] }; var empty_row: number[] = []; // build rows for vertical quiet zone if (QUIETV > 0) { empty_row = _array_fill(0, barcode_array.num_cols, 0) as number[]; for (var i = 0; i < QUIETV; ++i) { // add vertical quiet rows barcode_array.bcode.push(empty_row); } } var L: number = 0; var k = 0; // codeword index var cid = 0; // initial cluster // for each row for (var r = 0; r < rows; ++r) { // row start code var row = pstart; switch (cid) { case 0: { L = 30 * _intval(r / 3) + _intval((rows - 1) / 3); break; } case 1: { L = 30 * _intval(r / 3) + ecl * 3 + (rows - 1) % 3; break; } case 2: { L = 30 * _intval(r / 3) + (cols - 1); break; } } // left row indicator row += _sprintf("%17b", clusters[cid][L]); // for each column for (var c = 0; c < cols; ++c) { row += _sprintf("%17b", clusters[cid][codewords[k]]); ++k; } switch (cid) { case 0: { L = 30 * _intval(r / 3) + (cols - 1); break; } case 1: { L = 30 * _intval(r / 3) + _intval((rows - 1) / 3); break; } case 2: { L = 30 * _intval(r / 3) + ecl * 3 + (rows - 1) % 3; break; } } // right row indicator row += _sprintf("%17b", clusters[cid][L]); // row stop code row += pstop; // convert the string to array const arow = row.split(''); // duplicate row to get the desired height for (var h = 0; h < ROWHEIGHT; ++h) { barcode_array.bcode.push(arow.map(x => parseInt(x))); } ++cid; if (cid > 2) { cid = 0; } } if (QUIETV > 0) { for (var i = 0; i < QUIETV; ++i) { // add vertical quiet rows barcode_array.bcode.push(empty_row); } } return (barcode_array); };