// tslint:disable-next-line:no-circular-imports
|
import { ASCIIEncoder } from './ASCIIEncoder';
|
// tslint:disable-next-line:no-circular-imports
|
import { Base256Encoder } from './Base256Encoder';
|
// tslint:disable-next-line:no-circular-imports
|
import { C40Encoder } from './C40Encoder';
|
import { ASCII_ENCODATION, BASE256_ENCODATION, C40_ENCODATION, EDIFACT_ENCODATION, MACRO_05, MACRO_05_HEADER, MACRO_06, MACRO_06_HEADER, MACRO_TRAILER, PAD, TEXT_ENCODATION, X12_ENCODATION, } from './constants';
|
// tslint:disable-next-line:no-circular-imports
|
import { EdifactEncoder } from './EdifactEncoder';
|
import { EncoderContext } from './EncoderContext';
|
// tslint:disable-next-line:no-circular-imports
|
import { X12Encoder } from './X12Encoder';
|
// tslint:disable-next-line:no-circular-imports
|
import { TextEncoder } from './TextEncoder';
|
import Arrays from '../../util/Arrays';
|
import Integer from '../../util/Integer';
|
/**
|
* DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in
|
* annex S.
|
*/
|
class HighLevelEncoder {
|
static randomize253State(codewordPosition) {
|
const pseudoRandom = ((149 * codewordPosition) % 253) + 1;
|
const tempVariable = PAD + pseudoRandom;
|
return tempVariable <= 254 ? tempVariable : tempVariable - 254;
|
}
|
/**
|
* Performs message encoding of a DataMatrix message using the algorithm described in annex P
|
* of ISO/IEC 16022:2000(E).
|
*
|
* @param msg the message
|
* @param shape requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
|
* {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
|
* @param minSize the minimum symbol size constraint or null for no constraint
|
* @param maxSize the maximum symbol size constraint or null for no constraint
|
* @param forceC40 enforce C40 encoding
|
* @return the encoded message (the char values range from 0 to 255)
|
*/
|
static encodeHighLevel(msg, shape = 0 /* FORCE_NONE */, minSize = null, maxSize = null, forceC40 = false) {
|
// the codewords 0..255 are encoded as Unicode characters
|
const c40Encoder = new C40Encoder();
|
const encoders = [
|
new ASCIIEncoder(),
|
c40Encoder,
|
new TextEncoder(),
|
new X12Encoder(),
|
new EdifactEncoder(),
|
new Base256Encoder(),
|
];
|
const context = new EncoderContext(msg);
|
context.setSymbolShape(shape);
|
context.setSizeConstraints(minSize, maxSize);
|
if (msg.startsWith(MACRO_05_HEADER) && msg.endsWith(MACRO_TRAILER)) {
|
context.writeCodeword(MACRO_05);
|
context.setSkipAtEnd(2);
|
context.pos += MACRO_05_HEADER.length;
|
}
|
else if (msg.startsWith(MACRO_06_HEADER) && msg.endsWith(MACRO_TRAILER)) {
|
context.writeCodeword(MACRO_06);
|
context.setSkipAtEnd(2);
|
context.pos += MACRO_06_HEADER.length;
|
}
|
let encodingMode = ASCII_ENCODATION; // Default mode
|
if (forceC40) {
|
c40Encoder.encodeMaximal(context);
|
encodingMode = context.getNewEncoding();
|
context.resetEncoderSignal();
|
}
|
while (context.hasMoreCharacters()) {
|
encoders[encodingMode].encode(context);
|
if (context.getNewEncoding() >= 0) {
|
encodingMode = context.getNewEncoding();
|
context.resetEncoderSignal();
|
}
|
}
|
const len = context.getCodewordCount();
|
context.updateSymbolInfo();
|
const capacity = context.getSymbolInfo().getDataCapacity();
|
if (len < capacity &&
|
encodingMode !== ASCII_ENCODATION &&
|
encodingMode !== BASE256_ENCODATION &&
|
encodingMode !== EDIFACT_ENCODATION) {
|
context.writeCodeword('\u00fe'); // Unlatch (254)
|
}
|
// Padding
|
const codewords = context.getCodewords();
|
if (codewords.length() < capacity) {
|
codewords.append(PAD);
|
}
|
while (codewords.length() < capacity) {
|
codewords.append(this.randomize253State(codewords.length() + 1));
|
}
|
return context.getCodewords().toString();
|
}
|
static lookAheadTest(msg, startpos, currentMode) {
|
const newMode = this.lookAheadTestIntern(msg, startpos, currentMode);
|
if (currentMode === X12_ENCODATION && newMode === X12_ENCODATION) {
|
const endpos = Math.min(startpos + 3, msg.length);
|
for (let i = startpos; i < endpos; i++) {
|
if (!this.isNativeX12(msg.charCodeAt(i))) {
|
return ASCII_ENCODATION;
|
}
|
}
|
}
|
else if (currentMode === EDIFACT_ENCODATION &&
|
newMode === EDIFACT_ENCODATION) {
|
const endpos = Math.min(startpos + 4, msg.length);
|
for (let i = startpos; i < endpos; i++) {
|
if (!this.isNativeEDIFACT(msg.charCodeAt(i))) {
|
return ASCII_ENCODATION;
|
}
|
}
|
}
|
return newMode;
|
}
|
static lookAheadTestIntern(msg, startpos, currentMode) {
|
if (startpos >= msg.length) {
|
return currentMode;
|
}
|
let charCounts;
|
// step J
|
if (currentMode === ASCII_ENCODATION) {
|
charCounts = [0, 1, 1, 1, 1, 1.25];
|
}
|
else {
|
charCounts = [1, 2, 2, 2, 2, 2.25];
|
charCounts[currentMode] = 0;
|
}
|
let charsProcessed = 0;
|
const mins = new Uint8Array(6);
|
const intCharCounts = [];
|
while (true) {
|
// step K
|
if (startpos + charsProcessed === msg.length) {
|
Arrays.fill(mins, 0);
|
Arrays.fill(intCharCounts, 0);
|
const min = this.findMinimums(charCounts, intCharCounts, Integer.MAX_VALUE, mins);
|
const minCount = this.getMinimumCount(mins);
|
if (intCharCounts[ASCII_ENCODATION] === min) {
|
return ASCII_ENCODATION;
|
}
|
if (minCount === 1) {
|
if (mins[BASE256_ENCODATION] > 0) {
|
return BASE256_ENCODATION;
|
}
|
if (mins[EDIFACT_ENCODATION] > 0) {
|
return EDIFACT_ENCODATION;
|
}
|
if (mins[TEXT_ENCODATION] > 0) {
|
return TEXT_ENCODATION;
|
}
|
if (mins[X12_ENCODATION] > 0) {
|
return X12_ENCODATION;
|
}
|
}
|
return C40_ENCODATION;
|
}
|
const c = msg.charCodeAt(startpos + charsProcessed);
|
charsProcessed++;
|
// step L
|
if (this.isDigit(c)) {
|
charCounts[ASCII_ENCODATION] += 0.5;
|
}
|
else if (this.isExtendedASCII(c)) {
|
charCounts[ASCII_ENCODATION] = Math.ceil(charCounts[ASCII_ENCODATION]);
|
charCounts[ASCII_ENCODATION] += 2.0;
|
}
|
else {
|
charCounts[ASCII_ENCODATION] = Math.ceil(charCounts[ASCII_ENCODATION]);
|
charCounts[ASCII_ENCODATION]++;
|
}
|
// step M
|
if (this.isNativeC40(c)) {
|
charCounts[C40_ENCODATION] += 2.0 / 3.0;
|
}
|
else if (this.isExtendedASCII(c)) {
|
charCounts[C40_ENCODATION] += 8.0 / 3.0;
|
}
|
else {
|
charCounts[C40_ENCODATION] += 4.0 / 3.0;
|
}
|
// step N
|
if (this.isNativeText(c)) {
|
charCounts[TEXT_ENCODATION] += 2.0 / 3.0;
|
}
|
else if (this.isExtendedASCII(c)) {
|
charCounts[TEXT_ENCODATION] += 8.0 / 3.0;
|
}
|
else {
|
charCounts[TEXT_ENCODATION] += 4.0 / 3.0;
|
}
|
// step O
|
if (this.isNativeX12(c)) {
|
charCounts[X12_ENCODATION] += 2.0 / 3.0;
|
}
|
else if (this.isExtendedASCII(c)) {
|
charCounts[X12_ENCODATION] += 13.0 / 3.0;
|
}
|
else {
|
charCounts[X12_ENCODATION] += 10.0 / 3.0;
|
}
|
// step P
|
if (this.isNativeEDIFACT(c)) {
|
charCounts[EDIFACT_ENCODATION] += 3.0 / 4.0;
|
}
|
else if (this.isExtendedASCII(c)) {
|
charCounts[EDIFACT_ENCODATION] += 17.0 / 4.0;
|
}
|
else {
|
charCounts[EDIFACT_ENCODATION] += 13.0 / 4.0;
|
}
|
// step Q
|
if (this.isSpecialB256(c)) {
|
charCounts[BASE256_ENCODATION] += 4.0;
|
}
|
else {
|
charCounts[BASE256_ENCODATION]++;
|
}
|
// step R
|
if (charsProcessed >= 4) {
|
Arrays.fill(mins, 0);
|
Arrays.fill(intCharCounts, 0);
|
this.findMinimums(charCounts, intCharCounts, Integer.MAX_VALUE, mins);
|
if (intCharCounts[ASCII_ENCODATION] <
|
this.min(intCharCounts[BASE256_ENCODATION], intCharCounts[C40_ENCODATION], intCharCounts[TEXT_ENCODATION], intCharCounts[X12_ENCODATION], intCharCounts[EDIFACT_ENCODATION])) {
|
return ASCII_ENCODATION;
|
}
|
if (intCharCounts[BASE256_ENCODATION] < intCharCounts[ASCII_ENCODATION] ||
|
intCharCounts[BASE256_ENCODATION] + 1 <
|
this.min(intCharCounts[C40_ENCODATION], intCharCounts[TEXT_ENCODATION], intCharCounts[X12_ENCODATION], intCharCounts[EDIFACT_ENCODATION])) {
|
return BASE256_ENCODATION;
|
}
|
if (intCharCounts[EDIFACT_ENCODATION] + 1 <
|
this.min(intCharCounts[BASE256_ENCODATION], intCharCounts[C40_ENCODATION], intCharCounts[TEXT_ENCODATION], intCharCounts[X12_ENCODATION], intCharCounts[ASCII_ENCODATION])) {
|
return EDIFACT_ENCODATION;
|
}
|
if (intCharCounts[TEXT_ENCODATION] + 1 <
|
this.min(intCharCounts[BASE256_ENCODATION], intCharCounts[C40_ENCODATION], intCharCounts[EDIFACT_ENCODATION], intCharCounts[X12_ENCODATION], intCharCounts[ASCII_ENCODATION])) {
|
return TEXT_ENCODATION;
|
}
|
if (intCharCounts[X12_ENCODATION] + 1 <
|
this.min(intCharCounts[BASE256_ENCODATION], intCharCounts[C40_ENCODATION], intCharCounts[EDIFACT_ENCODATION], intCharCounts[TEXT_ENCODATION], intCharCounts[ASCII_ENCODATION])) {
|
return X12_ENCODATION;
|
}
|
if (intCharCounts[C40_ENCODATION] + 1 <
|
this.min(intCharCounts[ASCII_ENCODATION], intCharCounts[BASE256_ENCODATION], intCharCounts[EDIFACT_ENCODATION], intCharCounts[TEXT_ENCODATION])) {
|
if (intCharCounts[C40_ENCODATION] < intCharCounts[X12_ENCODATION]) {
|
return C40_ENCODATION;
|
}
|
if (intCharCounts[C40_ENCODATION] === intCharCounts[X12_ENCODATION]) {
|
let p = startpos + charsProcessed + 1;
|
while (p < msg.length) {
|
const tc = msg.charCodeAt(p);
|
if (this.isX12TermSep(tc)) {
|
return X12_ENCODATION;
|
}
|
if (!this.isNativeX12(tc)) {
|
break;
|
}
|
p++;
|
}
|
return C40_ENCODATION;
|
}
|
}
|
}
|
}
|
}
|
static min(f1, f2, f3, f4, f5) {
|
const val = Math.min(f1, Math.min(f2, Math.min(f3, f4)));
|
if (f5 === undefined) {
|
return val;
|
}
|
else {
|
return Math.min(val, f5);
|
}
|
}
|
static findMinimums(charCounts, intCharCounts, min, mins) {
|
for (let i = 0; i < 6; i++) {
|
const current = (intCharCounts[i] = Math.ceil(charCounts[i]));
|
if (min > current) {
|
min = current;
|
Arrays.fill(mins, 0);
|
}
|
if (min === current) {
|
mins[i] = mins[i] + 1;
|
}
|
}
|
return min;
|
}
|
static getMinimumCount(mins) {
|
let minCount = 0;
|
for (let i = 0; i < 6; i++) {
|
minCount += mins[i];
|
}
|
return minCount || 0;
|
}
|
static isDigit(ch) {
|
return ch >= '0'.charCodeAt(0) && ch <= '9'.charCodeAt(0);
|
}
|
static isExtendedASCII(ch) {
|
return ch >= 128 && ch <= 255;
|
}
|
static isNativeC40(ch) {
|
return (ch === ' '.charCodeAt(0) ||
|
(ch >= '0'.charCodeAt(0) && ch <= '9'.charCodeAt(0)) ||
|
(ch >= 'A'.charCodeAt(0) && ch <= 'Z'.charCodeAt(0)));
|
}
|
static isNativeText(ch) {
|
return (ch === ' '.charCodeAt(0) ||
|
(ch >= '0'.charCodeAt(0) && ch <= '9'.charCodeAt(0)) ||
|
(ch >= 'a'.charCodeAt(0) && ch <= 'z'.charCodeAt(0)));
|
}
|
static isNativeX12(ch) {
|
return (this.isX12TermSep(ch) ||
|
ch === ' '.charCodeAt(0) ||
|
(ch >= '0'.charCodeAt(0) && ch <= '9'.charCodeAt(0)) ||
|
(ch >= 'A'.charCodeAt(0) && ch <= 'Z'.charCodeAt(0)));
|
}
|
static isX12TermSep(ch) {
|
return (ch === 13 || // CR
|
ch === '*'.charCodeAt(0) ||
|
ch === '>'.charCodeAt(0));
|
}
|
static isNativeEDIFACT(ch) {
|
return ch >= ' '.charCodeAt(0) && ch <= '^'.charCodeAt(0);
|
}
|
static isSpecialB256(ch) {
|
return false; // TODO NOT IMPLEMENTED YET!!!
|
}
|
/**
|
* Determines the number of consecutive characters that are encodable using numeric compaction.
|
*
|
* @param msg the message
|
* @param startpos the start position within the message
|
* @return the requested character count
|
*/
|
static determineConsecutiveDigitCount(msg, startpos = 0) {
|
const len = msg.length;
|
let idx = startpos;
|
while (idx < len && this.isDigit(msg.charCodeAt(idx))) {
|
idx++;
|
}
|
return idx - startpos;
|
}
|
static illegalCharacter(singleCharacter) {
|
let hex = Integer.toHexString(singleCharacter.charCodeAt(0));
|
hex = '0000'.substring(0, 4 - hex.length) + hex;
|
throw new Error('Illegal character: ' + singleCharacter + ' (0x' + hex + ')');
|
}
|
}
|
export default HighLevelEncoder;
|