Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for DataMatrixWriter #549

Merged
merged 12 commits into from
Apr 19, 2023
348 changes: 244 additions & 104 deletions src/browser/BrowserCodeReader.ts

Large diffs are not rendered by default.

183 changes: 105 additions & 78 deletions src/core/EncodeHintType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,85 +22,112 @@
* @author dswitkin@google.com (Daniel Switkin)
*/
enum EncodeHintType {
/**
* Specifies what degree of error correction to use, for example in QR Codes.
* Type depends on the encoder. For example for QR codes it's type
* {@link com.google.zxing.qrcode.decoder.ErrorCorrectionLevel ErrorCorrectionLevel}.
* For Aztec it is of type {@link Integer}, representing the minimal percentage of error correction words.
* For PDF417 it is of type {@link Integer}, valid values being 0 to 8.
* In all cases, it can also be a {@link String} representation of the desired value as well.
* Note: an Aztec symbol should have a minimum of 25% EC words.
*/
ERROR_CORRECTION,

/**
* Specifies what degree of error correction to use, for example in QR Codes.
* Type depends on the encoder. For example for QR codes it's type
* {@link com.google.zxing.qrcode.decoder.ErrorCorrectionLevel ErrorCorrectionLevel}.
* For Aztec it is of type {@link Integer}, representing the minimal percentage of error correction words.
* For PDF417 it is of type {@link Integer}, valid values being 0 to 8.
* In all cases, it can also be a {@link String} representation of the desired value as well.
* Note: an Aztec symbol should have a minimum of 25% EC words.
*/
ERROR_CORRECTION,

/**
* Specifies what character encoding to use where applicable (type {@link String})
*/
CHARACTER_SET,

/**
* Specifies the matrix shape for Data Matrix (type {@link com.google.zxing.datamatrix.encoder.SymbolShapeHint})
*/
DATA_MATRIX_SHAPE,

/**
* Specifies a minimum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
*
* @deprecated use width/height params in
* {@link com.google.zxing.datamatrix.DataMatrixWriter#encode(String, BarcodeFormat, int, int)}
*/
/*@Deprecated*/
MIN_SIZE,

/**
* Specifies a maximum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
*
* @deprecated without replacement
*/
/*@Deprecated*/
MAX_SIZE,

/**
* Specifies margin, in pixels, to use when generating the barcode. The meaning can vary
* by format; for example it controls margin before and after the barcode horizontally for
* most 1D formats. (Type {@link Integer}, or {@link String} representation of the integer value).
*/
MARGIN,

/**
* Specifies whether to use compact mode for PDF417 (type {@link Boolean}, or "true" or "false"
* {@link String} value).
*/
PDF417_COMPACT,

/**
* Specifies what compaction mode to use for PDF417 (type
* {@link com.google.zxing.pdf417.encoder.Compaction Compaction} or {@link String} value of one of its
* enum values).
*/
PDF417_COMPACTION,

/**
* Specifies the minimum and maximum number of rows and columns for PDF417 (type
* {@link com.google.zxing.pdf417.encoder.Dimensions Dimensions}).
*/
PDF417_DIMENSIONS,

/**
* Specifies the required number of layers for an Aztec code.
* A negative number (-1, -2, -3, -4) specifies a compact Aztec code.
* 0 indicates to use the minimum number of layers (the default).
* A positive number (1, 2, .. 32) specifies a normal (non-compact) Aztec code.
* (Type {@link Integer}, or {@link String} representation of the integer value).
*/
AZTEC_LAYERS,

/**
* Specifies the exact version of QR code to be encoded.
* (Type {@link Integer}, or {@link String} representation of the integer value).
*/
QR_VERSION,
/**
* Specifies what character encoding to use where applicable (type {@link String})
*/
CHARACTER_SET,

/**
* Specifies the matrix shape for Data Matrix (type {@link com.google.zxing.datamatrix.encoder.SymbolShapeHint})
*/
DATA_MATRIX_SHAPE,

/**
* Specifies whether to use compact mode for Data Matrix (type {@link Boolean}, or "true" or "false"
* {@link String } value).
* The compact encoding mode also supports the encoding of characters that are not in the ISO-8859-1
* character set via ECIs.
* Please note that in that case, the most compact character encoding is chosen for characters in
* the input that are not in the ISO-8859-1 character set. Based on experience, some scanners do not
* support encodings like cp-1256 (Arabic). In such cases the encoding can be forced to UTF-8 by
* means of the {@link #CHARACTER_SET} encoding hint.
* Compact encoding also provides GS1-FNC1 support when {@link #GS1_FORMAT} is selected. In this case
* group-separator character (ASCII 29 decimal) can be used to encode the positions of FNC1 codewords
* for the purpose of delimiting AIs.
* This option and {@link #FORCE_C40} are mutually exclusive.
*/
DATA_MATRIX_COMPACT,

/**
* Specifies a minimum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
*
* @deprecated use width/height params in
* {@link com.google.zxing.datamatrix.DataMatrixWriter#encode(String, BarcodeFormat, int, int)}
*/
/*@Deprecated*/
MIN_SIZE,

/**
* Specifies a maximum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
*
* @deprecated without replacement
*/
/*@Deprecated*/
MAX_SIZE,

/**
* Specifies margin, in pixels, to use when generating the barcode. The meaning can vary
* by format; for example it controls margin before and after the barcode horizontally for
* most 1D formats. (Type {@link Integer}, or {@link String} representation of the integer value).
*/
MARGIN,

/**
* Specifies whether to use compact mode for PDF417 (type {@link Boolean}, or "true" or "false"
* {@link String} value).
*/
PDF417_COMPACT,

/**
* Specifies what compaction mode to use for PDF417 (type
* {@link com.google.zxing.pdf417.encoder.Compaction Compaction} or {@link String} value of one of its
* enum values).
*/
PDF417_COMPACTION,

/**
* Specifies the minimum and maximum number of rows and columns for PDF417 (type
* {@link com.google.zxing.pdf417.encoder.Dimensions Dimensions}).
*/
PDF417_DIMENSIONS,

/**
* Specifies the required number of layers for an Aztec code.
* A negative number (-1, -2, -3, -4) specifies a compact Aztec code.
* 0 indicates to use the minimum number of layers (the default).
* A positive number (1, 2, .. 32) specifies a normal (non-compact) Aztec code.
* (Type {@link Integer}, or {@link String} representation of the integer value).
*/
AZTEC_LAYERS,

/**
* Specifies the exact version of QR code to be encoded.
* (Type {@link Integer}, or {@link String} representation of the integer value).
*/
QR_VERSION,

/**
* Specifies whether the data should be encoded to the GS1 standard (type {@link Boolean}, or "true" or "false"
* {@link String } value).
*/
GS1_FORMAT,

/**
* Forces C40 encoding for data-matrix (type {@link Boolean}, or "true" or "false") {@link String } value). This
* option and {@link #DATA_MATRIX_COMPACT} are mutually exclusive.
*/
FORCE_C40,
}

export default EncodeHintType;
196 changes: 196 additions & 0 deletions src/core/common/ECIEncoderSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/**
* Set of CharsetEncoders for a given input string
*
* Invariants:
* - The list contains only encoders from CharacterSetECI (list is shorter then the list of encoders available on
* the platform for which ECI values are defined).
* - The list contains encoders at least one encoder for every character in the input.
* - The first encoder in the list is always the ISO-8859-1 encoder even of no character in the input can be encoded
* by it.
* - If the input contains a character that is not in ISO-8859-1 then the last two entries in the list will be the
* UTF-8 encoder and the UTF-16BE encoder.
*
* @author Alex Geller
*/

import { char } from '../../customTypings';
import Charset from '../util/Charset';
import StandardCharsets from '../util/StandardCharsets';
import StringEncoding from '../util/StringEncoding';
import StringUtils from './StringUtils';

class CharsetEncoder {
public name: string;

constructor(public readonly charset: Charset) {
this.name = charset.name;
}

public canEncode(c: string): boolean {
try {
return StringEncoding.encode(c, this.charset) != null;
} catch (ex) {
return false;
}
}
}

export class ECIEncoderSet {
private readonly ENCODERS: CharsetEncoder[] = [
'IBM437',
'ISO-8859-2',
'ISO-8859-3',
'ISO-8859-4',
'ISO-8859-5',
'ISO-8859-6',
'ISO-8859-7',
'ISO-8859-8',
'ISO-8859-9',
'ISO-8859-10',
'ISO-8859-11',
'ISO-8859-13',
'ISO-8859-14',
'ISO-8859-15',
'ISO-8859-16',
'windows-1250',
'windows-1251',
'windows-1252',
'windows-1256',
'Shift_JIS',
].map(name => new CharsetEncoder(Charset.forName(name)));
private encoders: CharsetEncoder[] = [];
private priorityEncoderIndex: number;

/**
* Constructs an encoder set
*
* @param stringToEncode the string that needs to be encoded
* @param priorityCharset The preferred {@link Charset} or null.
* @param fnc1 fnc1 denotes the character in the input that represents the FNC1 character or -1 for a non-GS1 bar
* code. When specified, it is considered an error to pass it as argument to the methods canEncode() or encode().
*/
constructor(stringToEncode: string, priorityCharset: Charset, fnc1: number) {
const neededEncoders: CharsetEncoder[] = [];

// we always need the ISO-8859-1 encoder. It is the default encoding
neededEncoders.push(new CharsetEncoder(StandardCharsets.ISO_8859_1));
let needUnicodeEncoder =
priorityCharset != null && priorityCharset.name.startsWith('UTF');

// Walk over the input string and see if all characters can be encoded with the list of encoders
for (let i = 0; i < stringToEncode.length; i++) {
let canEncode = false;
for (const encoder of neededEncoders) {
const singleCharacter = stringToEncode.charAt(i);
const c = singleCharacter.charCodeAt(0);

if (c === fnc1 || encoder.canEncode(singleCharacter)) {
canEncode = true;
break;
}
}

if (!canEncode) {
// for the character at position i we don't yet have an encoder in the list
for (const encoder of this.ENCODERS) {
if (encoder.canEncode(stringToEncode.charAt(i))) {
// Good, we found an encoder that can encode the character. We add him to the list and continue scanning
// the input
neededEncoders.push(encoder);
canEncode = true;
break;
}
}
}

if (!canEncode) {
// The character is not encodeable by any of the single byte encoders so we remember that we will need a
// Unicode encoder.
needUnicodeEncoder = true;
}
}

if (neededEncoders.length === 1 && !needUnicodeEncoder) {
// the entire input can be encoded by the ISO-8859-1 encoder
this.encoders = [neededEncoders[0]];
} else {
// we need more than one single byte encoder or we need a Unicode encoder.
// In this case we append a UTF-8 and UTF-16 encoder to the list
this.encoders = [];
let index = 0;
for (const encoder of neededEncoders) {
this.encoders[index++] = encoder;
}

// this.encoders[index] = new CharsetEncoder(StandardCharsets.UTF_8);
// this.encoders[index + 1] = new CharsetEncoder(StandardCharsets.UTF_16BE);
}

// Compute priorityEncoderIndex by looking up priorityCharset in encoders
let priorityEncoderIndexValue = -1;
if (priorityCharset != null) {
for (let i = 0; i < this.encoders.length; i++) {
if (
this.encoders[i] != null &&
priorityCharset.name === this.encoders[i].name
) {
priorityEncoderIndexValue = i;
break;
}
}
}
this.priorityEncoderIndex = priorityEncoderIndexValue;

// invariants
// if(this?.encoders?.[0].name !== StandardCharsets.ISO_8859_1)){
// throw new Error("ISO-8859-1 must be the first encoder");
// }
}

public length(): number {
return this.encoders.length;
}

public getCharsetName(index: number): string {
if (!(index < this.length())) {
throw new Error('index must be less than length');
}
return this.encoders[index].name;
}

public getCharset(index: number): Charset {
if (!(index < this.length())) {
throw new Error('index must be less than length');
}
return this.encoders[index].charset;
}

public getECIValue(encoderIndex: number): number {
return this.encoders[encoderIndex].charset.getValueIdentifier();
}

/*
* returns -1 if no priority charset was defined
*/
public getPriorityEncoderIndex(): number {
return this.priorityEncoderIndex;
}

public canEncode(c: char, encoderIndex: number): boolean {
if (!(encoderIndex < this.length())) {
throw new Error('index must be less than length');
}
return true;
}

public encode(c: char, encoderIndex: number): Uint8Array {
if (!(encoderIndex < this.length())) {
throw new Error('index must be less than length');
}

return StringEncoding.encode(
StringUtils.getCharAt(c),
this.encoders[encoderIndex].name
);
}
}
Loading