From 12d52b6b1be4a5bf3b1a324388e706f07cb7bc6b Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 27 Oct 2022 15:37:43 -0500 Subject: [PATCH] porting encoders --- src/datamatrix/encoder/ASCIIEncoder.java | 82 --- src/datamatrix/encoder/C40Encoder.java | 212 ------- src/datamatrix/encoder/HighLevelEncoder.java | 484 ---------------- src/datamatrix/encoder/TextEncoder.java | 81 --- src/datamatrix/encoder/ascii_encoder.rs | 114 ++++ src/datamatrix/encoder/c40_encoder.rs | 274 +++++++++ src/datamatrix/encoder/encoder.rs | 8 +- src/datamatrix/encoder/encoder_context.rs | 28 +- src/datamatrix/encoder/high_level_encoder.rs | 576 +++++++++++++++++++ src/datamatrix/encoder/mod.rs | 10 + src/datamatrix/encoder/text_encoder.rs | 87 +++ 11 files changed, 1080 insertions(+), 876 deletions(-) delete mode 100644 src/datamatrix/encoder/ASCIIEncoder.java delete mode 100644 src/datamatrix/encoder/C40Encoder.java delete mode 100644 src/datamatrix/encoder/HighLevelEncoder.java delete mode 100644 src/datamatrix/encoder/TextEncoder.java create mode 100644 src/datamatrix/encoder/ascii_encoder.rs create mode 100644 src/datamatrix/encoder/c40_encoder.rs create mode 100644 src/datamatrix/encoder/high_level_encoder.rs create mode 100644 src/datamatrix/encoder/text_encoder.rs diff --git a/src/datamatrix/encoder/ASCIIEncoder.java b/src/datamatrix/encoder/ASCIIEncoder.java deleted file mode 100644 index 866c3fe..0000000 --- a/src/datamatrix/encoder/ASCIIEncoder.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2006-2007 Jeremias Maerki. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.datamatrix.encoder; - -final class ASCIIEncoder implements Encoder { - - @Override - public int getEncodingMode() { - return HighLevelEncoder.ASCII_ENCODATION; - } - - @Override - public void encode(EncoderContext context) { - //step B - int n = HighLevelEncoder.determineConsecutiveDigitCount(context.getMessage(), context.pos); - if (n >= 2) { - context.writeCodeword(encodeASCIIDigits(context.getMessage().charAt(context.pos), - context.getMessage().charAt(context.pos + 1))); - context.pos += 2; - } else { - char c = context.getCurrentChar(); - int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode()); - if (newMode != getEncodingMode()) { - switch (newMode) { - case HighLevelEncoder.BASE256_ENCODATION: - context.writeCodeword(HighLevelEncoder.LATCH_TO_BASE256); - context.signalEncoderChange(HighLevelEncoder.BASE256_ENCODATION); - return; - case HighLevelEncoder.C40_ENCODATION: - context.writeCodeword(HighLevelEncoder.LATCH_TO_C40); - context.signalEncoderChange(HighLevelEncoder.C40_ENCODATION); - return; - case HighLevelEncoder.X12_ENCODATION: - context.writeCodeword(HighLevelEncoder.LATCH_TO_ANSIX12); - context.signalEncoderChange(HighLevelEncoder.X12_ENCODATION); - break; - case HighLevelEncoder.TEXT_ENCODATION: - context.writeCodeword(HighLevelEncoder.LATCH_TO_TEXT); - context.signalEncoderChange(HighLevelEncoder.TEXT_ENCODATION); - break; - case HighLevelEncoder.EDIFACT_ENCODATION: - context.writeCodeword(HighLevelEncoder.LATCH_TO_EDIFACT); - context.signalEncoderChange(HighLevelEncoder.EDIFACT_ENCODATION); - break; - default: - throw new IllegalStateException("Illegal mode: " + newMode); - } - } else if (HighLevelEncoder.isExtendedASCII(c)) { - context.writeCodeword(HighLevelEncoder.UPPER_SHIFT); - context.writeCodeword((char) (c - 128 + 1)); - context.pos++; - } else { - context.writeCodeword((char) (c + 1)); - context.pos++; - } - - } - } - - private static char encodeASCIIDigits(char digit1, char digit2) { - if (HighLevelEncoder.isDigit(digit1) && HighLevelEncoder.isDigit(digit2)) { - int num = (digit1 - 48) * 10 + (digit2 - 48); - return (char) (num + 130); - } - throw new IllegalArgumentException("not digits: " + digit1 + digit2); - } - -} diff --git a/src/datamatrix/encoder/C40Encoder.java b/src/datamatrix/encoder/C40Encoder.java deleted file mode 100644 index 1194bdd..0000000 --- a/src/datamatrix/encoder/C40Encoder.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2006-2007 Jeremias Maerki. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.datamatrix.encoder; - -class C40Encoder implements Encoder { - - @Override - public int getEncodingMode() { - return HighLevelEncoder.C40_ENCODATION; - } - - void encodeMaximal(EncoderContext context) { - StringBuilder buffer = new StringBuilder(); - int lastCharSize = 0; - int backtrackStartPosition = context.pos; - int backtrackBufferLength = 0; - while (context.hasMoreCharacters()) { - char c = context.getCurrentChar(); - context.pos++; - lastCharSize = encodeChar(c, buffer); - if (buffer.length() % 3 == 0) { - backtrackStartPosition = context.pos; - backtrackBufferLength = buffer.length(); - } - } - if (backtrackBufferLength != buffer.length()) { - int unwritten = (buffer.length() / 3) * 2; - - int curCodewordCount = context.getCodewordCount() + unwritten + 1; // +1 for the latch to C40 - context.updateSymbolInfo(curCodewordCount); - int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount; - int rest = buffer.length() % 3; - if ((rest == 2 && available != 2) || - (rest == 1 && (lastCharSize > 3 || available != 1))) { - buffer.setLength(backtrackBufferLength); - context.pos = backtrackStartPosition; - } - } - if (buffer.length() > 0) { - context.writeCodeword(HighLevelEncoder.LATCH_TO_C40); - } - - handleEOD(context, buffer); - } - - @Override - public void encode(EncoderContext context) { - //step C - StringBuilder buffer = new StringBuilder(); - while (context.hasMoreCharacters()) { - char c = context.getCurrentChar(); - context.pos++; - - int lastCharSize = encodeChar(c, buffer); - - int unwritten = (buffer.length() / 3) * 2; - - int curCodewordCount = context.getCodewordCount() + unwritten; - context.updateSymbolInfo(curCodewordCount); - int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount; - - if (!context.hasMoreCharacters()) { - //Avoid having a single C40 value in the last triplet - StringBuilder removed = new StringBuilder(); - if ((buffer.length() % 3) == 2 && available != 2) { - lastCharSize = backtrackOneCharacter(context, buffer, removed, lastCharSize); - } - while ((buffer.length() % 3) == 1 && (lastCharSize > 3 || available != 1)) { - lastCharSize = backtrackOneCharacter(context, buffer, removed, lastCharSize); - } - break; - } - - int count = buffer.length(); - if ((count % 3) == 0) { - int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode()); - if (newMode != getEncodingMode()) { - // Return to ASCII encodation, which will actually handle latch to new mode - context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); - break; - } - } - } - handleEOD(context, buffer); - } - - private int backtrackOneCharacter(EncoderContext context, - StringBuilder buffer, StringBuilder removed, int lastCharSize) { - int count = buffer.length(); - buffer.delete(count - lastCharSize, count); - context.pos--; - char c = context.getCurrentChar(); - lastCharSize = encodeChar(c, removed); - context.resetSymbolInfo(); //Deal with possible reduction in symbol size - return lastCharSize; - } - - static void writeNextTriplet(EncoderContext context, StringBuilder buffer) { - context.writeCodewords(encodeToCodewords(buffer)); - buffer.delete(0, 3); - } - - /** - * Handle "end of data" situations - * - * @param context the encoder context - * @param buffer the buffer with the remaining encoded characters - */ - void handleEOD(EncoderContext context, StringBuilder buffer) { - int unwritten = (buffer.length() / 3) * 2; - int rest = buffer.length() % 3; - - int curCodewordCount = context.getCodewordCount() + unwritten; - context.updateSymbolInfo(curCodewordCount); - int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount; - - if (rest == 2) { - buffer.append('\0'); //Shift 1 - while (buffer.length() >= 3) { - writeNextTriplet(context, buffer); - } - if (context.hasMoreCharacters()) { - context.writeCodeword(HighLevelEncoder.C40_UNLATCH); - } - } else if (available == 1 && rest == 1) { - while (buffer.length() >= 3) { - writeNextTriplet(context, buffer); - } - if (context.hasMoreCharacters()) { - context.writeCodeword(HighLevelEncoder.C40_UNLATCH); - } - // else no unlatch - context.pos--; - } else if (rest == 0) { - while (buffer.length() >= 3) { - writeNextTriplet(context, buffer); - } - if (available > 0 || context.hasMoreCharacters()) { - context.writeCodeword(HighLevelEncoder.C40_UNLATCH); - } - } else { - throw new IllegalStateException("Unexpected case. Please report!"); - } - context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION); - } - - int encodeChar(char c, StringBuilder sb) { - if (c == ' ') { - sb.append('\3'); - return 1; - } - if (c >= '0' && c <= '9') { - sb.append((char) (c - 48 + 4)); - return 1; - } - if (c >= 'A' && c <= 'Z') { - sb.append((char) (c - 65 + 14)); - return 1; - } - if (c < ' ') { - sb.append('\0'); //Shift 1 Set - sb.append(c); - return 2; - } - if (c <= '/') { - sb.append('\1'); //Shift 2 Set - sb.append((char) (c - 33)); - return 2; - } - if (c <= '@') { - sb.append('\1'); //Shift 2 Set - sb.append((char) (c - 58 + 15)); - return 2; - } - if (c <= '_') { - sb.append('\1'); //Shift 2 Set - sb.append((char) (c - 91 + 22)); - return 2; - } - if (c <= 127) { - sb.append('\2'); //Shift 3 Set - sb.append((char) (c - 96)); - return 2; - } - sb.append("\1\u001e"); //Shift 2, Upper Shift - int len = 2; - len += encodeChar((char) (c - 128), sb); - return len; - } - - private static String encodeToCodewords(CharSequence sb) { - int v = (1600 * sb.charAt(0)) + (40 * sb.charAt(1)) + sb.charAt(2) + 1; - char cw1 = (char) (v / 256); - char cw2 = (char) (v % 256); - return new String(new char[] {cw1, cw2}); - } - -} diff --git a/src/datamatrix/encoder/HighLevelEncoder.java b/src/datamatrix/encoder/HighLevelEncoder.java deleted file mode 100644 index 0016ce7..0000000 --- a/src/datamatrix/encoder/HighLevelEncoder.java +++ /dev/null @@ -1,484 +0,0 @@ -/* - * Copyright 2006-2007 Jeremias Maerki. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.datamatrix.encoder; - -import com.google.zxing.Dimension; - -import java.util.Arrays; - -/** - * DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in - * annex S. - */ -public final class HighLevelEncoder { - - /** - * Padding character - */ - private static final char PAD = 129; - /** - * mode latch to C40 encodation mode - */ - static final char LATCH_TO_C40 = 230; - /** - * mode latch to Base 256 encodation mode - */ - static final char LATCH_TO_BASE256 = 231; - /** - * FNC1 Codeword - */ - //private static final char FNC1 = 232; - /** - * Structured Append Codeword - */ - //private static final char STRUCTURED_APPEND = 233; - /** - * Reader Programming - */ - //private static final char READER_PROGRAMMING = 234; - /** - * Upper Shift - */ - static final char UPPER_SHIFT = 235; - /** - * 05 Macro - */ - private static final char MACRO_05 = 236; - /** - * 06 Macro - */ - private static final char MACRO_06 = 237; - /** - * mode latch to ANSI X.12 encodation mode - */ - static final char LATCH_TO_ANSIX12 = 238; - /** - * mode latch to Text encodation mode - */ - static final char LATCH_TO_TEXT = 239; - /** - * mode latch to EDIFACT encodation mode - */ - static final char LATCH_TO_EDIFACT = 240; - /** - * ECI character (Extended Channel Interpretation) - */ - //private static final char ECI = 241; - - /** - * Unlatch from C40 encodation - */ - static final char C40_UNLATCH = 254; - /** - * Unlatch from X12 encodation - */ - static final char X12_UNLATCH = 254; - - /** - * 05 Macro header - */ - static final String MACRO_05_HEADER = "[)>\u001E05\u001D"; - /** - * 06 Macro header - */ - static final String MACRO_06_HEADER = "[)>\u001E06\u001D"; - /** - * Macro trailer - */ - static final String MACRO_TRAILER = "\u001E\u0004"; - - static final int ASCII_ENCODATION = 0; - static final int C40_ENCODATION = 1; - static final int TEXT_ENCODATION = 2; - static final int X12_ENCODATION = 3; - static final int EDIFACT_ENCODATION = 4; - static final int BASE256_ENCODATION = 5; - - private HighLevelEncoder() { - } - - private static char randomize253State(int codewordPosition) { - int pseudoRandom = ((149 * codewordPosition) % 253) + 1; - int tempVariable = PAD + pseudoRandom; - return (char) (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 - * @return the encoded message (the char values range from 0 to 255) - */ - public static String encodeHighLevel(String msg) { - return encodeHighLevel(msg, SymbolShapeHint.FORCE_NONE, null, null, false); - } - - /** - * 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 - * @return the encoded message (the char values range from 0 to 255) - */ - public static String encodeHighLevel(String msg, - SymbolShapeHint shape, - Dimension minSize, - Dimension maxSize) { - return encodeHighLevel(msg, shape, minSize, maxSize, false); - } - /** - * 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) - */ - public static String encodeHighLevel(String msg, - SymbolShapeHint shape, - Dimension minSize, - Dimension maxSize, - boolean forceC40) { - //the codewords 0..255 are encoded as Unicode characters - C40Encoder c40Encoder = new C40Encoder(); - Encoder[] encoders = { - new ASCIIEncoder(), c40Encoder, new TextEncoder(), - new X12Encoder(), new EdifactEncoder(), new Base256Encoder() - }; - - EncoderContext 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(); - } - - int 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(); - } - } - int len = context.getCodewordCount(); - context.updateSymbolInfo(); - int capacity = context.getSymbolInfo().getDataCapacity(); - if (len < capacity && - encodingMode != ASCII_ENCODATION && - encodingMode != BASE256_ENCODATION && - encodingMode != EDIFACT_ENCODATION) { - context.writeCodeword('\u00fe'); //Unlatch (254) - } - //Padding - StringBuilder codewords = context.getCodewords(); - if (codewords.length() < capacity) { - codewords.append(PAD); - } - while (codewords.length() < capacity) { - codewords.append(randomize253State(codewords.length() + 1)); - } - - return context.getCodewords().toString(); - } - - static int lookAheadTest(CharSequence msg, int startpos, int currentMode) { - int newMode = lookAheadTestIntern(msg, startpos, currentMode); - if (currentMode == X12_ENCODATION && newMode == X12_ENCODATION) { - int endpos = Math.min(startpos + 3, msg.length()); - for (int i = startpos; i < endpos; i++) { - if (!isNativeX12(msg.charAt(i))) { - return ASCII_ENCODATION; - } - } - } else if (currentMode == EDIFACT_ENCODATION && newMode == EDIFACT_ENCODATION) { - int endpos = Math.min(startpos + 4, msg.length()); - for (int i = startpos; i < endpos; i++) { - if (!isNativeEDIFACT(msg.charAt(i))) { - return ASCII_ENCODATION; - } - } - } - return newMode; - } - - static int lookAheadTestIntern(CharSequence msg, int startpos, int currentMode) { - if (startpos >= msg.length()) { - return currentMode; - } - float[] charCounts; - //step J - if (currentMode == ASCII_ENCODATION) { - charCounts = new float[]{0, 1, 1, 1, 1, 1.25f}; - } else { - charCounts = new float[]{1, 2, 2, 2, 2, 2.25f}; - charCounts[currentMode] = 0; - } - - int charsProcessed = 0; - byte[] mins = new byte[6]; - int[] intCharCounts = new int[6]; - while (true) { - //step K - if ((startpos + charsProcessed) == msg.length()) { - Arrays.fill(mins, (byte) 0); - Arrays.fill(intCharCounts, 0); - int min = findMinimums(charCounts, intCharCounts, Integer.MAX_VALUE, mins); - int minCount = 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; - } - - char c = msg.charAt(startpos + charsProcessed); - charsProcessed++; - - //step L - if (isDigit(c)) { - charCounts[ASCII_ENCODATION] += 0.5f; - } else if (isExtendedASCII(c)) { - charCounts[ASCII_ENCODATION] = (float) Math.ceil(charCounts[ASCII_ENCODATION]); - charCounts[ASCII_ENCODATION] += 2.0f; - } else { - charCounts[ASCII_ENCODATION] = (float) Math.ceil(charCounts[ASCII_ENCODATION]); - charCounts[ASCII_ENCODATION]++; - } - - //step M - if (isNativeC40(c)) { - charCounts[C40_ENCODATION] += 2.0f / 3.0f; - } else if (isExtendedASCII(c)) { - charCounts[C40_ENCODATION] += 8.0f / 3.0f; - } else { - charCounts[C40_ENCODATION] += 4.0f / 3.0f; - } - - //step N - if (isNativeText(c)) { - charCounts[TEXT_ENCODATION] += 2.0f / 3.0f; - } else if (isExtendedASCII(c)) { - charCounts[TEXT_ENCODATION] += 8.0f / 3.0f; - } else { - charCounts[TEXT_ENCODATION] += 4.0f / 3.0f; - } - - //step O - if (isNativeX12(c)) { - charCounts[X12_ENCODATION] += 2.0f / 3.0f; - } else if (isExtendedASCII(c)) { - charCounts[X12_ENCODATION] += 13.0f / 3.0f; - } else { - charCounts[X12_ENCODATION] += 10.0f / 3.0f; - } - - //step P - if (isNativeEDIFACT(c)) { - charCounts[EDIFACT_ENCODATION] += 3.0f / 4.0f; - } else if (isExtendedASCII(c)) { - charCounts[EDIFACT_ENCODATION] += 17.0f / 4.0f; - } else { - charCounts[EDIFACT_ENCODATION] += 13.0f / 4.0f; - } - - // step Q - if (isSpecialB256(c)) { - charCounts[BASE256_ENCODATION] += 4.0f; - } else { - charCounts[BASE256_ENCODATION]++; - } - - //step R - if (charsProcessed >= 4) { - Arrays.fill(mins, (byte) 0); - Arrays.fill(intCharCounts, 0); - findMinimums(charCounts, intCharCounts, Integer.MAX_VALUE, mins); - - if (intCharCounts[ASCII_ENCODATION] < 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 < min(intCharCounts[C40_ENCODATION], - intCharCounts[TEXT_ENCODATION], intCharCounts[X12_ENCODATION], intCharCounts[EDIFACT_ENCODATION])) { - return BASE256_ENCODATION; - } - if (intCharCounts[EDIFACT_ENCODATION] + 1 < min(intCharCounts[BASE256_ENCODATION], - intCharCounts[C40_ENCODATION] , intCharCounts[TEXT_ENCODATION] , intCharCounts[X12_ENCODATION], - intCharCounts[ASCII_ENCODATION])) { - return EDIFACT_ENCODATION; - } - if (intCharCounts[TEXT_ENCODATION] + 1 < min(intCharCounts[BASE256_ENCODATION], - intCharCounts[C40_ENCODATION] , intCharCounts[EDIFACT_ENCODATION] , intCharCounts[X12_ENCODATION], - intCharCounts[ASCII_ENCODATION])) { - return TEXT_ENCODATION; - } - if (intCharCounts[X12_ENCODATION] + 1 < min(intCharCounts[BASE256_ENCODATION], - intCharCounts[C40_ENCODATION] , intCharCounts[EDIFACT_ENCODATION] , intCharCounts[TEXT_ENCODATION], - intCharCounts[ASCII_ENCODATION])) { - return X12_ENCODATION; - } - if (intCharCounts[C40_ENCODATION] + 1 < 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]) { - int p = startpos + charsProcessed + 1; - while (p < msg.length()) { - char tc = msg.charAt(p); - if (isX12TermSep(tc)) { - return X12_ENCODATION; - } - if (!isNativeX12(tc)) { - break; - } - p++; - } - return C40_ENCODATION; - } - } - } - } - } - - private static int min(int f1, int f2, int f3, int f4, int f5) { - return Math.min(min(f1, f2, f3, f4),f5); - } - - private static int min(int f1, int f2, int f3, int f4) { - return Math.min(f1, Math.min(f2, Math.min(f3, f4))); - } - - private static int findMinimums(float[] charCounts, int[] intCharCounts, int min, byte[] mins) { - for (int i = 0; i < 6; i++) { - int current = (intCharCounts[i] = (int) Math.ceil(charCounts[i])); - if (min > current) { - min = current; - Arrays.fill(mins, (byte) 0); - } - if (min == current) { - mins[i]++; - } - } - return min; - } - - private static int getMinimumCount(byte[] mins) { - int minCount = 0; - for (int i = 0; i < 6; i++) { - minCount += mins[i]; - } - return minCount; - } - - static boolean isDigit(char ch) { - return ch >= '0' && ch <= '9'; - } - - static boolean isExtendedASCII(char ch) { - return ch >= 128 && ch <= 255; - } - - static boolean isNativeC40(char ch) { - return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'); - } - - static boolean isNativeText(char ch) { - return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z'); - } - - static boolean isNativeX12(char ch) { - return isX12TermSep(ch) || (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'); - } - - private static boolean isX12TermSep(char ch) { - return (ch == '\r') //CR - || (ch == '*') - || (ch == '>'); - } - - static boolean isNativeEDIFACT(char ch) { - return ch >= ' ' && ch <= '^'; - } - - private static boolean isSpecialB256(char 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 - */ - public static int determineConsecutiveDigitCount(CharSequence msg, int startpos) { - int len = msg.length(); - int idx = startpos; - while (idx < len && isDigit(msg.charAt(idx))) { - idx++; - } - return idx - startpos; - } - - static void illegalCharacter(char c) { - String hex = Integer.toHexString(c); - hex = "0000".substring(0, 4 - hex.length()) + hex; - throw new IllegalArgumentException("Illegal character: " + c + " (0x" + hex + ')'); - } - -} diff --git a/src/datamatrix/encoder/TextEncoder.java b/src/datamatrix/encoder/TextEncoder.java deleted file mode 100644 index 24286ac..0000000 --- a/src/datamatrix/encoder/TextEncoder.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2006-2007 Jeremias Maerki. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.datamatrix.encoder; - -final class TextEncoder extends C40Encoder { - - @Override - public int getEncodingMode() { - return HighLevelEncoder.TEXT_ENCODATION; - } - - @Override - int encodeChar(char c, StringBuilder sb) { - if (c == ' ') { - sb.append('\3'); - return 1; - } - if (c >= '0' && c <= '9') { - sb.append((char) (c - 48 + 4)); - return 1; - } - if (c >= 'a' && c <= 'z') { - sb.append((char) (c - 97 + 14)); - return 1; - } - if (c < ' ') { - sb.append('\0'); //Shift 1 Set - sb.append(c); - return 2; - } - if (c <= '/') { - sb.append('\1'); //Shift 2 Set - sb.append((char) (c - 33)); - return 2; - } - if (c <= '@') { - sb.append('\1'); //Shift 2 Set - sb.append((char) (c - 58 + 15)); - return 2; - } - if (c >= '[' && c <= '_') { - sb.append('\1'); //Shift 2 Set - sb.append((char) (c - 91 + 22)); - return 2; - } - if (c == '`') { - sb.append('\2'); //Shift 3 Set - sb.append((char) 0); // '`' - 96 == 0 - return 2; - } - if (c <= 'Z') { - sb.append('\2'); //Shift 3 Set - sb.append((char) (c - 65 + 1)); - return 2; - } - if (c <= 127) { - sb.append('\2'); //Shift 3 Set - sb.append((char) (c - 123 + 27)); - return 2; - } - sb.append("\1\u001e"); //Shift 2, Upper Shift - int len = 2; - len += encodeChar((char) (c - 128), sb); - return len; - } - -} diff --git a/src/datamatrix/encoder/ascii_encoder.rs b/src/datamatrix/encoder/ascii_encoder.rs new file mode 100644 index 0000000..adc8001 --- /dev/null +++ b/src/datamatrix/encoder/ascii_encoder.rs @@ -0,0 +1,114 @@ +/* + * Copyright 2006-2007 Jeremias Maerki. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::Exceptions; + +use super::{high_level_encoder, Encoder}; + +pub struct ASCIIEncoder; + +impl Encoder for ASCIIEncoder { + fn encode(&self, context: &mut super::EncoderContext) -> Result<(), Exceptions> { + //step B + let n = + high_level_encoder::determineConsecutiveDigitCount(context.getMessage(), context.pos); + if n >= 2 { + context.writeCodeword(Self::encodeASCIIDigits( + context + .getMessage() + .chars() + .nth(context.pos as usize) + .unwrap(), + context + .getMessage() + .chars() + .nth(context.pos as usize + 1) + .unwrap(), + )? as u8); + context.pos += 2; + } else { + let c = context.getCurrentChar(); + let newMode = high_level_encoder::lookAheadTest( + context.getMessage(), + context.pos, + self.getEncodingMode() as u32, + ); + if newMode != self.getEncodingMode() { + match newMode { + high_level_encoder::BASE256_ENCODATION => { + context.writeCodeword(high_level_encoder::LATCH_TO_BASE256); + context.signalEncoderChange(high_level_encoder::BASE256_ENCODATION); + return Ok(()); + } + high_level_encoder::C40_ENCODATION => { + context.writeCodeword(high_level_encoder::LATCH_TO_C40); + context.signalEncoderChange(high_level_encoder::C40_ENCODATION); + return Ok(()); + } + high_level_encoder::X12_ENCODATION => { + context.writeCodeword(high_level_encoder::LATCH_TO_ANSIX12); + context.signalEncoderChange(high_level_encoder::X12_ENCODATION); + } + high_level_encoder::TEXT_ENCODATION => { + context.writeCodeword(high_level_encoder::LATCH_TO_TEXT); + context.signalEncoderChange(high_level_encoder::TEXT_ENCODATION); + } + + high_level_encoder::EDIFACT_ENCODATION => { + context.writeCodeword(high_level_encoder::LATCH_TO_EDIFACT); + context.signalEncoderChange(high_level_encoder::EDIFACT_ENCODATION); + } + + _ => { + return Err(Exceptions::IllegalStateException(format!( + "Illegal mode: {}", + newMode + ))); + } + } + } else if high_level_encoder::isExtendedASCII(c) { + context.writeCodeword(high_level_encoder::UPPER_SHIFT); + context.writeCodeword(c as u8 - 128 + 1); + context.pos += 1; + } else { + context.writeCodeword(c as u8 + 1); + context.pos += 1; + } + } + Ok(()) + } + + fn getEncodingMode(&self) -> usize { + high_level_encoder::ASCII_ENCODATION + } +} + +impl ASCIIEncoder { + pub fn new() -> Self { + Self + } + fn encodeASCIIDigits(digit1: char, digit2: char) -> Result { + if high_level_encoder::isDigit(digit1) && high_level_encoder::isDigit(digit2) { + let num = (digit1 as u8 - 48) * 10 + (digit2 as u8 - 48); + Ok((num + 130) as char) + } else { + Err(Exceptions::IllegalArgumentException(format!( + "not digits: {}{}", + digit1, digit2 + ))) + } + } +} diff --git a/src/datamatrix/encoder/c40_encoder.rs b/src/datamatrix/encoder/c40_encoder.rs new file mode 100644 index 0000000..11f8185 --- /dev/null +++ b/src/datamatrix/encoder/c40_encoder.rs @@ -0,0 +1,274 @@ +/* + * Copyright 2006-2007 Jeremias Maerki. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crate::Exceptions; + +use super::high_level_encoder::{ + self, ASCII_ENCODATION, C40_ENCODATION, C40_UNLATCH, LATCH_TO_C40, +}; + +use super::{Encoder, EncoderContext}; + +pub struct C40Encoder; + +impl Encoder for C40Encoder { + fn encode(&self, context: &mut super::EncoderContext) -> Result<(), Exceptions> { + self.encode_with_encode_char_fn(context, &Self::encodeChar_c40) + } + + fn getEncodingMode(&self) -> usize { + C40_ENCODATION + } +} + +impl C40Encoder { + pub fn new() -> Self { + Self + } + + pub fn encode_with_encode_char_fn( + &self, + context: &mut super::EncoderContext, + encodeChar: &dyn Fn(char, &mut String) -> u32, + ) -> Result<(), Exceptions> { + //step C + let mut buffer = String::new(); + while context.hasMoreCharacters() { + let c = context.getCurrentChar(); + context.pos += 1; + + let lastCharSize = encodeChar(c, &mut buffer); + + let unwritten = (buffer.len() / 3) * 2; + + let curCodewordCount = context.getCodewordCount() + unwritten; + context.updateSymbolInfoWithLength(curCodewordCount); + let available = + context.getSymbolInfo().unwrap().getDataCapacity() as usize - curCodewordCount; + + if !context.hasMoreCharacters() { + //Avoid having a single C40 value in the last triplet + let mut removed = String::new(); + if (buffer.len() % 3) == 2 && available != 2 { + lastCharSize = self.backtrackOneCharacter( + context, + &mut buffer, + &mut removed, + lastCharSize, + encodeChar, + ); + } + while (buffer.len() % 3) == 1 && (lastCharSize > 3 || available != 1) { + lastCharSize = self.backtrackOneCharacter( + context, + &mut buffer, + &removed, + lastCharSize, + encodeChar, + ); + } + break; + } + + let count = buffer.len(); + if (count % 3) == 0 { + let newMode = high_level_encoder::lookAheadTest( + context.getMessage(), + context.pos, + self.getEncodingMode() as u32, + ); + if newMode != self.getEncodingMode() { + // Return to ASCII encodation, which will actually handle latch to new mode + context.signalEncoderChange(ASCII_ENCODATION); + break; + } + } + } + self.handleEOD(context, &mut buffer); + Ok(()) + } + + fn encodeMaximal( + &self, + context: &EncoderContext, + encodeChar: &dyn Fn(char, &mut String) -> u32, + ) { + let buffer = String::new(); + let lastCharSize = 0; + let backtrackStartPosition = context.pos; + let backtrackBufferLength = 0; + while context.hasMoreCharacters() { + let c = context.getCurrentChar(); + context.pos += 1; + lastCharSize = encodeChar(c, &mut buffer); + if buffer.len() % 3 == 0 { + backtrackStartPosition = context.pos; + backtrackBufferLength = buffer.len(); + } + } + if backtrackBufferLength != buffer.len() { + let unwritten = (buffer.len() / 3) * 2; + + let curCodewordCount = context.getCodewordCount() + unwritten + 1; // +1 for the latch to C40 + context.updateSymbolInfoWithLength(curCodewordCount); + let available = + context.getSymbolInfo().unwrap().getDataCapacity() as usize - curCodewordCount; + let rest = buffer.len() % 3; + if (rest == 2 && available != 2) || (rest == 1 && (lastCharSize > 3 || available != 1)) + { + buffer.truncate(backtrackBufferLength); + // buffer.setLength(backtrackBufferLength); + context.pos = backtrackStartPosition; + } + } + if buffer.len() > 0 { + context.writeCodeword(LATCH_TO_C40); + } + + self.handleEOD(context, &mut buffer); + } + + fn backtrackOneCharacter( + &self, + context: &EncoderContext, + buffer: &mut String, + removed: &String, + lastCharSize: u32, + encodeChar: &dyn Fn(char, &mut String) -> u32, + ) -> u32 { + let count = buffer.len(); + // buffer.delete(count - lastCharSize, count); + buffer.replace_range((count - lastCharSize as usize)..count, ""); + context.pos -= 1; + let c = context.getCurrentChar(); + lastCharSize = encodeChar(c, &mut removed); + context.resetSymbolInfo(); //Deal with possible reduction in symbol size + return lastCharSize; + } + + fn writeNextTriplet(context: &EncoderContext, buffer: &mut String) { + context.writeCodewords(&Self::encodeToCodewords(buffer)); + buffer.replace_range(0..3, ""); + // buffer.delete(0, 3); + } + + /** + * Handle "end of data" situations + * + * @param context the encoder context + * @param buffer the buffer with the remaining encoded characters + */ + fn handleEOD(&self, context: &EncoderContext, buffer: &mut String) -> Result<(), Exceptions> { + let unwritten = (buffer.len() / 3) * 2; + let rest = buffer.len() % 3; + + let curCodewordCount = context.getCodewordCount() + unwritten; + context.updateSymbolInfoWithLength(curCodewordCount); + let available = + context.getSymbolInfo().unwrap().getDataCapacity() as usize - curCodewordCount; + + if rest == 2 { + buffer.push('\0'); //Shift 1 + while buffer.len() >= 3 { + C40Encoder::writeNextTriplet(context, buffer); + } + if context.hasMoreCharacters() { + context.writeCodeword(C40_UNLATCH); + } + } else if available == 1 && rest == 1 { + while buffer.len() >= 3 { + C40Encoder::writeNextTriplet(context, buffer); + } + if context.hasMoreCharacters() { + context.writeCodeword(C40_UNLATCH); + } + // else no unlatch + context.pos -= 1; + } else if rest == 0 { + while buffer.len() >= 3 { + C40Encoder::writeNextTriplet(context, buffer); + } + if available > 0 || context.hasMoreCharacters() { + context.writeCodeword(C40_UNLATCH); + } + } else { + return Err(Exceptions::IllegalStateException( + "Unexpected case. Please report!".to_owned(), + )); + } + context.signalEncoderChange(ASCII_ENCODATION); + + Ok(()) + } + + fn encodeChar_c40(c: char, sb: &mut String) -> u32 { + if c == ' ' { + sb.push('\u{3}'); + return 1; + } + if c >= '0' && c <= '9' { + sb.push((c as u8 - 48 + 4) as char); + return 1; + } + if c >= 'A' && c <= 'Z' { + sb.push((c as u8 - 65 + 14) as char); + return 1; + } + if c < ' ' { + sb.push('\0'); //Shift 1 Set + sb.push(c); + return 2; + } + if c <= '/' { + sb.push('\u{1}'); //Shift 2 Set + sb.push((c as u8 - 33) as char); + return 2; + } + if c <= '@' { + sb.push('\u{1}'); //Shift 2 Set + sb.push((c as u8 - 58 + 15) as char); + return 2; + } + if c <= '_' { + sb.push('\u{1}'); //Shift 2 Set + sb.push((c as u8 - 91 + 22) as char); + return 2; + } + if (c as u8) <= 127 { + sb.push('\u{2}'); //Shift 3 Set + sb.push((c as u8 - 96) as char); + return 2; + } + sb.push_str("\u{1}\u{001e}"); //Shift 2, Upper Shift + let len = 2; + len += Self::encodeChar_c40((c as u8 - 128) as char, sb); + + len + } + + fn encodeToCodewords(sb: &str) -> String { + let v = (1600 * sb.chars().nth(0).unwrap() as u32) + + (40 * sb.chars().nth(1).unwrap() as u32) + + sb.chars().nth(2).unwrap() as u32 + + 1; + let cw1 = v / 256; + let cw2 = v % 256; + [char::from_u32(cw1).unwrap(), char::from_u32(cw2).unwrap()] + .into_iter() + .collect() + // return new String(new char[] {cw1, cw2}); + } +} diff --git a/src/datamatrix/encoder/encoder.rs b/src/datamatrix/encoder/encoder.rs index 7270014..907f38a 100644 --- a/src/datamatrix/encoder/encoder.rs +++ b/src/datamatrix/encoder/encoder.rs @@ -14,12 +14,12 @@ * limitations under the License. */ +use crate::Exceptions; + use super::EncoderContext; pub trait Encoder { + fn getEncodingMode(&self) -> usize; - fn getEncodingMode(&self) -> i32; - - fn encode(&self, context:&mut EncoderContext); - + fn encode(&self, context: &mut EncoderContext) -> Result<(), Exceptions>; } diff --git a/src/datamatrix/encoder/encoder_context.rs b/src/datamatrix/encoder/encoder_context.rs index dae22c1..fc9a537 100644 --- a/src/datamatrix/encoder/encoder_context.rs +++ b/src/datamatrix/encoder/encoder_context.rs @@ -31,8 +31,8 @@ pub struct EncoderContext<'a> { minSize: Option, maxSize: Option, codewords: String, - pos: u32, - newEncoding: i32, + pub(super) pos: u32, + newEncoding:Option< usize>, symbolInfo: Option<&'a SymbolInfo>, skipAtEnd: u32, } @@ -65,7 +65,7 @@ impl EncoderContext<'_> { msg: sb, shape: SymbolShapeHint::FORCE_NONE, codewords: String::with_capacity(msg.len()), - newEncoding: -1, + newEncoding: None, minSize: None, maxSize: None, pos: 0, @@ -91,12 +91,14 @@ impl EncoderContext<'_> { self.skipAtEnd = count; } - pub fn getCurrentChar(&self) -> &str { - self.msg.graphemes(true).nth(self.pos as usize).unwrap() + pub fn getCurrentChar(&self) -> char { + // self.msg.graphemes(true).nth(self.pos as usize).unwrap() + self.msg.chars().nth(self.pos as usize).unwrap() } - pub fn getCurrent(&self) -> &str { - self.msg.graphemes(true).nth(self.pos as usize).unwrap() + pub fn getCurrent(&self) -> char { + // self.msg.graphemes(true).nth(self.pos as usize).unwrap() + self.msg.chars().nth(self.pos as usize).unwrap() } pub fn getCodewords(&self) -> &str { @@ -107,24 +109,24 @@ impl EncoderContext<'_> { self.codewords.push_str(codewords); } - pub fn writeCodeword(&mut self, codeword: &str) { - self.codewords.push_str(codeword); + pub fn writeCodeword(&mut self, codeword: u8) { + self.codewords.push(codeword as char); } pub fn getCodewordCount(&self) -> usize { self.codewords.len() } - pub fn getNewEncoding(&self) -> i32 { + pub fn getNewEncoding(&self) -> Option { self.newEncoding } - pub fn signalEncoderChange(&mut self, encoding: i32) { - self.newEncoding = encoding; + pub fn signalEncoderChange(&mut self, encoding: usize) { + self.newEncoding = Some(encoding); } pub fn resetEncoderSignal(&mut self) { - self.newEncoding = -1; + self.newEncoding = None; } pub fn hasMoreCharacters(&self) -> bool { diff --git a/src/datamatrix/encoder/high_level_encoder.rs b/src/datamatrix/encoder/high_level_encoder.rs new file mode 100644 index 0000000..3586305 --- /dev/null +++ b/src/datamatrix/encoder/high_level_encoder.rs @@ -0,0 +1,576 @@ +/* + * Copyright 2006-2007 Jeremias Maerki. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use std::rc::Rc; + +use encoding::{self, EncodingRef}; + +use crate::{Dimension, Exceptions}; + +use super::{EncoderContext, SymbolShapeHint, C40Encoder, ASCIIEncoder, Encoder, TextEncoder}; +const DEFAULT_ENCODING: EncodingRef = encoding::all::ISO_8859_1; + +use unicode_segmentation::UnicodeSegmentation; + +/** + * DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in + * annex S. + */ +/** + * Padding character + */ +const PAD: u8 = 129; +/** + * mode latch to C40 encodation mode + */ +pub const LATCH_TO_C40: u8 = 230; +/** + * mode latch to Base 256 encodation mode + */ +pub const LATCH_TO_BASE256: u8 = 231; +/** + * FNC1 Codeword + */ +//private static final char FNC1 = 232; +/** + * Structured Append Codeword + */ +//private static final char STRUCTURED_APPEND = 233; +/** + * Reader Programming + */ +//private static final char READER_PROGRAMMING = 234; +/** + * Upper Shift + */ +pub const UPPER_SHIFT: u8 = 235; +/** + * 05 Macro + */ +const MACRO_05: u8 = 236; +/** + * 06 Macro + */ +const MACRO_06: u8 = 237; +/** + * mode latch to ANSI X.12 encodation mode + */ +pub const LATCH_TO_ANSIX12: u8 = 238; +/** + * mode latch to Text encodation mode + */ +pub const LATCH_TO_TEXT: u8 = 239; +/** + * mode latch to EDIFACT encodation mode + */ +pub const LATCH_TO_EDIFACT: u8 = 240; +/** + * ECI character (Extended Channel Interpretation) + */ +//private static final char ECI = 241; + +/** + * Unlatch from C40 encodation + */ +pub const C40_UNLATCH: u8 = 254; +/** + * Unlatch from X12 encodation + */ +pub const X12_UNLATCH: u8 = 254; + +/** + * 05 Macro header + */ +pub const MACRO_05_HEADER: &str = "[)>\u{001E}05\u{001D}"; // THIS MIGHT BE WRONG, CHECK IF IT SHOULD BE a long unicode +/** + * 06 Macro header + */ +pub const MACRO_06_HEADER: &str = "[)>\u{001E}06\u{001D}"; // THIS MIGHT BE WRONG, CHECK IF IT SHOULD BE a long unicode +/** + * Macro trailer + */ +pub const MACRO_TRAILER: &str = "\u{001E}\u{0004}"; + +pub const ASCII_ENCODATION: usize = 0; +pub const C40_ENCODATION: usize = 1; +pub const TEXT_ENCODATION: usize = 2; +pub const X12_ENCODATION: usize = 3; +pub const EDIFACT_ENCODATION: usize = 4; +pub const BASE256_ENCODATION: usize = 5; + +fn randomize253State(codewordPosition: u32) -> String { + let pseudoRandom = ((149 * codewordPosition) % 253) + 1; + let tempVariable = PAD as u32 + pseudoRandom; + if tempVariable <= 254 { + char::from_u32(tempVariable) + } else { + char::from_u32(tempVariable - 254) + } + .expect("must become a char") + .to_string() + // return (char) (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 + * @return the encoded message (the char values range from 0 to 255) + */ +pub fn encodeHighLevel(msg: &str) -> Result { + encodeHighLevelWithDimensionForceC40(msg, SymbolShapeHint::FORCE_NONE, None, None, false) +} + +/** + * 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 + * @return the encoded message (the char values range from 0 to 255) + */ +pub fn encodeHighLevelWithDimension( + msg: &str, + shape: SymbolShapeHint, + minSize: Option, + maxSize: Option, +) -> Result { + encodeHighLevelWithDimensionForceC40(msg, shape, minSize, maxSize, false) +} +/** + * 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) + */ +pub fn encodeHighLevelWithDimensionForceC40( + msg: &str, + shape: SymbolShapeHint, + minSize: Option, + maxSize: Option, + forceC40: bool, +) -> Result { + //the codewords 0..255 are encoded as Unicode characters + let c40Encoder = Rc::new(C40Encoder::new()); + let encoders:[Rc;6] = [ + Rc::new(ASCIIEncoder::new()), + c40Encoder.clone(), + Rc::new(TextEncoder::new()), + X12Encoder::new(), + EdifactEncoder::new(), + Base256Encoder::new(), + ]; + + let context = EncoderContext::new(msg)?; + context.setSymbolShape(shape); + context.setSizeConstraints(minSize, maxSize); + + if msg.starts_with(MACRO_05_HEADER) && msg.ends_with(MACRO_TRAILER) { + context.writeCodeword(MACRO_05); + context.setSkipAtEnd(2); + context.pos += MACRO_05_HEADER.len(); + } else if (msg.starts_with(MACRO_06_HEADER) && msg.ends_with(MACRO_TRAILER)) { + context.writeCodeword(MACRO_06); + context.setSkipAtEnd(2); + context.pos += MACRO_06_HEADER.len(); + } + + 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(); + } + } + let len = context.getCodewordCount(); + context.updateSymbolInfo(); + let capacity = context.getSymbolInfo().getDataCapacity(); + if len < capacity + && encodingMode != ASCII_ENCODATION + && encodingMode != BASE256_ENCODATION + && encodingMode != EDIFACT_ENCODATION + { + context.writeCodeword("\u{00fe}"); //Unlatch (254) + } + //Padding + let codewords = context.getCodewords(); + if codewords.len() < capacity { + codewords.append(PAD); + } + while codewords.len() < capacity { + codewords.append(randomize253State(codewords.length() + 1)); + } + + return context.getCodewords().toString(); +} + +pub fn lookAheadTest(msg: &str, startpos: u32, currentMode: u32) -> usize { + let newMode = lookAheadTestIntern(msg, startpos, currentMode); + if currentMode as usize == X12_ENCODATION && newMode as usize == X12_ENCODATION { + let msg_graphemes = msg.graphemes(true); + let endpos = (startpos + 3).min(msg_graphemes.count() as u32); + for i in startpos..endpos { + // for (int i = startpos; i < endpos; i++) { + if !isNativeX12(msg_graphemes.nth(i as usize).unwrap()) { + return ASCII_ENCODATION; + } + } + } else if currentMode as usize == EDIFACT_ENCODATION && newMode == EDIFACT_ENCODATION { + let msg_graphemes = msg.graphemes(true); + let endpos = (startpos + 4).min(msg_graphemes.count() as u32); + for i in startpos..endpos { + // for (int i = startpos; i < endpos; i++) { + if !isNativeEDIFACT(msg_graphemes.nth(i as usize).unwrap()) { + return ASCII_ENCODATION; + } + } + } + newMode +} + +fn lookAheadTestIntern(msg: &str, startpos: u32, currentMode: u32) -> usize { + if startpos as usize >= msg.len() { + return currentMode as usize; + } + let charCounts: [f32; 6]; + //step J + if currentMode == ASCII_ENCODATION as u32 { + charCounts = [0.0, 1.0, 1.0, 1.0, 1.0, 1.25]; + } else { + charCounts = [1.0, 2.0, 2.0, 2.0, 2.0, 2.25]; + charCounts[currentMode as usize] = 0.0; + } + + let charsProcessed = 0; + let mins = [0u8; 6]; + let intCharCounts = [0u32; 6]; + loop { + //step K + if (startpos + charsProcessed) == msg.len() as u32 { + mins.fill(0); + intCharCounts.fill(0); + // Arrays.fill(mins, (byte) 0); + // Arrays.fill(intCharCounts, 0); + let min = findMinimums(&charCounts, &intCharCounts, u32::MAX, &mins); + let minCount = 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; + } + + let c = msg + .graphemes(true) + .nth((startpos + charsProcessed) as usize) + .unwrap(); + // let c = msg.charAt(startpos + charsProcessed); + charsProcessed += 1; + + //step L + if isDigit(c) { + charCounts[ASCII_ENCODATION] += 0.5; + } else if isExtendedASCII(c) { + charCounts[ASCII_ENCODATION] = charCounts[ASCII_ENCODATION].ceil(); + charCounts[ASCII_ENCODATION] += 2.0; + } else { + charCounts[ASCII_ENCODATION] = charCounts[ASCII_ENCODATION].ceil(); + charCounts[ASCII_ENCODATION] += 1.0; + } + + //step M + if isNativeC40(c) { + charCounts[C40_ENCODATION] += 2.0 / 3.0; + } else if isExtendedASCII(c) { + charCounts[C40_ENCODATION] += 8.0 / 3.0; + } else { + charCounts[C40_ENCODATION] += 4.0 / 3.0; + } + + //step N + if isNativeText(c) { + charCounts[TEXT_ENCODATION] += 2.0 / 3.0; + } else if isExtendedASCII(c) { + charCounts[TEXT_ENCODATION] += 8.0 / 3.0; + } else { + charCounts[TEXT_ENCODATION] += 4.0 / 3.0; + } + + //step O + if isNativeX12(c) { + charCounts[X12_ENCODATION] += 2.0 / 3.0; + } else if isExtendedASCII(c) { + charCounts[X12_ENCODATION] += 13.0 / 3.0; + } else { + charCounts[X12_ENCODATION] += 10.0 / 3.0; + } + + //step P + if isNativeEDIFACT(c) { + charCounts[EDIFACT_ENCODATION] += 3.0 / 4.0; + } else if isExtendedASCII(c) { + charCounts[EDIFACT_ENCODATION] += 17.0 / 4.0; + } else { + charCounts[EDIFACT_ENCODATION] += 13.0 / 4.0; + } + + // step Q + if isSpecialB256(c) { + charCounts[BASE256_ENCODATION] += 4.0; + } else { + charCounts[BASE256_ENCODATION] += 1.0; + } + + //step R + if charsProcessed >= 4 { + mins.fill(0); + intCharCounts.fill(0); + // Arrays.fill(mins, (byte) 0); + // Arrays.fill(intCharCounts, 0); + findMinimums(&charCounts, &intCharCounts, u32::MAX, &mins); + + if intCharCounts[ASCII_ENCODATION] + < min5( + 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 + < min4( + intCharCounts[C40_ENCODATION], + intCharCounts[TEXT_ENCODATION], + intCharCounts[X12_ENCODATION], + intCharCounts[EDIFACT_ENCODATION], + ) + { + return BASE256_ENCODATION; + } + if intCharCounts[EDIFACT_ENCODATION] + 1 + < min5( + intCharCounts[BASE256_ENCODATION], + intCharCounts[C40_ENCODATION], + intCharCounts[TEXT_ENCODATION], + intCharCounts[X12_ENCODATION], + intCharCounts[ASCII_ENCODATION], + ) + { + return EDIFACT_ENCODATION; + } + if intCharCounts[TEXT_ENCODATION] + 1 + < min5( + intCharCounts[BASE256_ENCODATION], + intCharCounts[C40_ENCODATION], + intCharCounts[EDIFACT_ENCODATION], + intCharCounts[X12_ENCODATION], + intCharCounts[ASCII_ENCODATION], + ) + { + return TEXT_ENCODATION; + } + if intCharCounts[X12_ENCODATION] + 1 + < min5( + intCharCounts[BASE256_ENCODATION], + intCharCounts[C40_ENCODATION], + intCharCounts[EDIFACT_ENCODATION], + intCharCounts[TEXT_ENCODATION], + intCharCounts[ASCII_ENCODATION], + ) + { + return X12_ENCODATION; + } + if intCharCounts[C40_ENCODATION] + 1 + < min4( + 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; + for tc in msg.graphemes(true) { + // while (p as usize) < msg.len() { + // let tc = msg.charAt(p); + if isX12TermSep(tc) { + return X12_ENCODATION; + } + if !isNativeX12(tc) { + break; + } + p += 1; + } + return C40_ENCODATION; + } + } + } + } +} + +fn min5(f1: u32, f2: u32, f3: u32, f4: u32, f5: u32) -> u32 { + min4(f1, f2, f3, f4).min(f5) +} + +fn min4(f1: u32, f2: u32, f3: u32, f4: u32) -> u32 { + f1.min(f2.min(f3.min(f4))) + // Math.min(f1, Math.min(f2, Math.min(f3, f4))) +} + +fn findMinimums(charCounts: &[f32; 6], intCharCounts: &[u32; 6], min: u32, mins: &[u8]) -> u32 { + for i in 0..6 { + // for (int i = 0; i < 6; i++) { + intCharCounts[i] = charCounts[i].ceil() as u32; + let current = intCharCounts[i]; // = (int) Math.ceil(charCounts[i])); + if min > current { + min = current; + mins.fill(0); + // Arrays.fill(mins, (byte) 0); + } + if min == current { + mins[i] += 1; + } + } + return min; +} + +fn getMinimumCount(mins: &[u8]) -> u32 { + let minCount = 0; + for i in 0..6 { + // for (int i = 0; i < 6; i++) { + minCount += mins[i] as u32; + } + minCount +} + +pub fn isDigit(ch: char) -> bool { + ch >= '0' && ch <= '9' +} + +pub fn isExtendedASCII(ch: char) -> bool { + (ch as u8) >= 128 && (ch as u8) <= 255 +} + +fn isNativeC40(ch: &str) -> bool { + if ch.len() > 1 { + return false; + } + let cha = ch.chars().nth(0).unwrap(); + (cha == ' ') || (cha >= '0' && cha <= '9') || (cha >= 'A' && cha <= 'Z') +} + +fn isNativeText(ch: &str) -> bool { + if ch.len() > 1 { + return false; + } + let cha = ch.chars().nth(0).unwrap(); + (cha == ' ') || (cha >= '0' && cha <= '9') || (cha >= 'a' && cha <= 'z') +} + +fn isNativeX12(ch: &str) -> bool { + if ch.len() > 1 { + return false; + } + let cha = ch.chars().nth(0).unwrap(); + return isX12TermSep(ch) + || (cha == ' ') + || (cha >= '0' && cha <= '9') + || (cha >= 'A' && cha <= 'Z'); +} + +fn isX12TermSep(ch: &str) -> bool { + (ch == "\r") //CR + || (ch == "*") + || (ch == ">") +} + +fn isNativeEDIFACT(ch: &str) -> bool { + if ch.len() > 1 { + return false; + } + let cha = ch.chars().nth(0).unwrap(); + cha >= ' ' && cha <= '^' +} + +fn isSpecialB256(ch: &str) -> bool { + unimplemented!(); + 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 + */ +pub fn determineConsecutiveDigitCount(msg: &str, startpos: u32) -> u32 { + // let len = msg.len(); + let idx = startpos; + let graphemes = msg.graphemes(true); + while (idx as usize) < graphemes.count() && isDigit(graphemes.nth(idx as usize).unwrap()) { + idx += 1; + } + idx - startpos +} + +fn illegalCharacter(c: &str) -> Result<(), Exceptions> { + // let hex = Integer.toHexString(c); + // hex = "0000".substring(0, 4 - hex.length()) + hex; + Err(Exceptions::IllegalArgumentException(format!( + "Illegal character: {} (0x{})", + c, c + ))) +} diff --git a/src/datamatrix/encoder/mod.rs b/src/datamatrix/encoder/mod.rs index 272b716..8316997 100644 --- a/src/datamatrix/encoder/mod.rs +++ b/src/datamatrix/encoder/mod.rs @@ -2,8 +2,18 @@ mod encoder; mod encoder_context; mod symbol_shape_hint; mod symbol_info; +pub mod high_level_encoder; pub use encoder::*; pub use encoder_context::*; pub use symbol_shape_hint::*; pub use symbol_info::*; + +mod c40_encoder; +pub use c40_encoder::*; + +mod ascii_encoder; +pub use ascii_encoder::*; + +mod text_encoder; +pub use text_encoder::*; \ No newline at end of file diff --git a/src/datamatrix/encoder/text_encoder.rs b/src/datamatrix/encoder/text_encoder.rs new file mode 100644 index 0000000..5912a4f --- /dev/null +++ b/src/datamatrix/encoder/text_encoder.rs @@ -0,0 +1,87 @@ +/* + * Copyright 2006-2007 Jeremias Maerki. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::{high_level_encoder, C40Encoder, Encoder}; + +pub struct TextEncoder(C40Encoder); +impl Encoder for TextEncoder { + fn getEncodingMode(&self) -> usize { + high_level_encoder::TEXT_ENCODATION + } + + fn encode(&self, context: &mut super::EncoderContext) -> Result<(), crate::Exceptions> { + self.0 + .encode_with_encode_char_fn(context, &Self::encodeChar) + } +} +impl TextEncoder { + pub fn new() -> Self { + Self(C40Encoder::new()) + } + fn encodeChar(c: char, sb: &mut String) -> u32 { + if c == ' ' { + sb.push('\u{3}'); + return 1; + } + if c >= '0' && c <= '9' { + sb.push((c as u8 - 48 + 4) as char); + return 1; + } + if c >= 'a' && c <= 'z' { + sb.push((c as u8 - 97 + 14) as char); + return 1; + } + if c < ' ' { + sb.push('\0'); //Shift 1 Set + sb.push(c); + return 2; + } + if c <= '/' { + sb.push('\u{1}'); //Shift 2 Set + sb.push((c as u8 - 33) as char); + return 2; + } + if c <= '@' { + sb.push('\u{1}'); //Shift 2 Set + sb.push((c as u8 - 58 + 15) as char); + return 2; + } + if c >= '[' && c <= '_' { + sb.push('\u{1}'); //Shift 2 Set + sb.push((c as u8 - 91 + 22) as char); + return 2; + } + if c == '`' { + sb.push('\u{2}'); //Shift 3 Set + sb.push(0 as char); // '`' - 96 == 0 + return 2; + } + if c <= 'Z' { + sb.push('\u{2}'); //Shift 3 Set + sb.push((c as u8 - 65 + 1) as char); + return 2; + } + if c as u8 <= 127 { + sb.push('\u{2}'); //Shift 3 Set + sb.push((c as u8 - 123 + 27) as char); + return 2; + } + sb.push_str("\u{1}\u{001e}"); //Shift 2, Upper Shift + let len = 2; + len += Self::encodeChar((c as u8 - 128) as char, sb); + return len; + } +}