diff --git a/src/main/java/uk/org/okapibarcode/backend/QrCode.java b/src/main/java/uk/org/okapibarcode/backend/QrCode.java index 66edef0c..76ac65ff 100644 --- a/src/main/java/uk/org/okapibarcode/backend/QrCode.java +++ b/src/main/java/uk/org/okapibarcode/backend/QrCode.java @@ -16,12 +16,17 @@ package uk.org.okapibarcode.backend; +import uk.org.okapibarcode.util.EciMode; + import static uk.org.okapibarcode.util.Arrays.positionOf; import static uk.org.okapibarcode.util.Strings.binaryAppend; import java.nio.CharBuffer; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** *
Implements QR Code bar code symbology According to ISO/IEC 18004:2015. @@ -175,12 +180,19 @@ private enum QrMode { 0x2542e, 0x26a64, 0x27541, 0x28c69 }; + /* The max number of symbols in a structured append sequence. */ + private static final int MAX_STRUCTURED_APPEND_SYMBOLS = 16; + protected int minVersion = 1; protected int preferredVersion; protected EccLevel preferredEccLevel = EccLevel.L; protected boolean improveEccLevelIfPossible = true; protected boolean forceByteCompaction; + protected int structuredAppendPosition = 1; + protected int structuredAppendTotal = 1; + protected int structuredAppendParity = 0; + /** * Creates a new instance. */ @@ -295,6 +307,69 @@ public boolean getForceByteCompaction() { return forceByteCompaction; } + /** + * Sets the position of this QR Code symbol in a structured append sequence (1–16). + * + * @param position the position of this QR Code symbol in a structured append sequence (1–16) + */ + public void setStructuredAppendPosition(int position) { + if (position < 1 || position > MAX_STRUCTURED_APPEND_SYMBOLS) { + throw new IllegalArgumentException("Invalid QR Code structured append position: " + position); + } + this.structuredAppendPosition = position; + } + + /** + * Returns the position of this QR Code symbol in a structured append sequence (1-16). + * + * @return the position of this QR Code symbol in a structured append sequence (1–16) + */ + public int getStructuredAppendPosition() { + return structuredAppendPosition; + } + + /** + * Sets the total number of QR Code symbols in the structured append sequence (1–16). + * + * @param total the total number of QR Code symbols in the structured append sequence (1–16) + */ + public void setStructuredAppendTotal(int total) { + if (total < 1 || total > MAX_STRUCTURED_APPEND_SYMBOLS) { + throw new IllegalArgumentException("Invalid QR Code structured append total: " + total); + } + this.structuredAppendTotal = total; + } + + /** + * Returns the total number of QR Code symbols in the structured append sequence (1-16). + * + * @return the total number of QR Code symbols in the structured append sequence (1–16) + */ + public int getStructuredAppendTotal() { + return structuredAppendTotal; + } + + /** + * Sets the structured append parity (XOR of all bytes in the original message, 0–255). + * + * @param parity the parity value (0–255) + */ + public void setStructuredAppendParity(int parity) { + if (parity < 0 || parity > 255) { + throw new IllegalArgumentException("Invalid QR Code structured append parity: " + parity); + } + this.structuredAppendParity = parity; + } + + /** + * Returns the structured append parity (XOR of all bytes in the original message, 0–255). + * + * @return the parity value (0–255) + */ + public int getStructuredAppendParity() { + return structuredAppendParity; + } + @Override public boolean supportsGs1() { return true; @@ -344,7 +419,7 @@ protected void encode() { QrMode[] inputMode = new QrMode[inputData.length]; defineMode(inputMode, inputData, forceByteCompaction); - est_binlen = getBinaryLength(40, inputMode, inputData, gs1, eciMode); + est_binlen = getBinaryLength(40, inputMode, inputData, gs1, eciMode, getStructuredAppendMode()); ecc_level = this.preferredEccLevel; switch (ecc_level) { @@ -390,7 +465,7 @@ protected void encode() { dataCodewords = QR_DATA_CODEWORDS_H; break; } - int proposedBinLen = getBinaryLength(candidate, inputMode, inputData, gs1, eciMode); + int proposedBinLen = getBinaryLength(candidate, inputMode, inputData, gs1, eciMode, getStructuredAppendMode()); if ((8 * dataCodewords[candidate - 1]) >= proposedBinLen) { version = candidate; est_binlen = proposedBinLen; @@ -481,7 +556,7 @@ protected void encode() { */ if (preferredVersion > version) { version = preferredVersion; - est_binlen = getBinaryLength(preferredVersion, inputMode, inputData, gs1, eciMode); + est_binlen = getBinaryLength(preferredVersion, inputMode, inputData, gs1, eciMode, getStructuredAppendMode()); inputMode = applyOptimisation(version, inputMode); } if (preferredVersion < version) { @@ -629,7 +704,7 @@ private static void defineMode(QrMode[] inputMode, int[] inputData, boolean forc } /** Calculate the actual bit length of the proposed binary string. */ - private static int getBinaryLength(int version, QrMode[] inputModeUnoptimized, int[] inputData, boolean gs1, int eciMode) { + private static int getBinaryLength(int version, QrMode[] inputModeUnoptimized, int[] inputData, boolean gs1, int eciMode, boolean structuredAppendMode) { int i, j; QrMode currentMode; @@ -645,6 +720,10 @@ private static int getBinaryLength(int version, QrMode[] inputModeUnoptimized, i currentMode = QrMode.NULL; + if (structuredAppendMode) { + count += 20; + } + if (gs1) { count += 4; } @@ -920,6 +999,13 @@ private void qrBinary(int[] datastream, int version, int target_binlen, QrMode[] int reserved = est_binlen + 12; StringBuilder binary = new StringBuilder(reserved); + if (getStructuredAppendMode()) { + binary.append("0011"); /* Structured append mode */ + binaryAppend(binary, structuredAppendPosition - 1, 4); + binaryAppend(binary, structuredAppendTotal - 1, 4); + binaryAppend(binary, structuredAppendParity, 8); + } + if (gs1) { binary.append("0101"); /* FNC1 */ } @@ -1156,6 +1242,10 @@ private void qrBinary(int[] datastream, int version, int target_binlen, QrMode[] assert binary.length() <= reserved; } + private boolean getStructuredAppendMode() { + return structuredAppendTotal > 1; + } + /** Splits data into blocks, adds error correction and then interleaves the blocks and error correction data. */ private static void addEcc(int[] fullstream, int[] datastream, int version, int data_cw, int blocks) { @@ -1761,4 +1851,129 @@ private static void addVersionInfo(int[] grid, int size, int version) { protected void customize(int[] grid, int size) { // empty } + + /** + * Creates a list of QR Code symbols for structured append from a string, using a template symbol. + * The template's settings are cloned for each symbol. + * + * @param data the input data + * @param template the template QrCode symbol + * @return a list of QrCode symbols with structured append set + * @throws OkapiException if no data or data is invalid + */ + public static List< QrCode > createStructuredAppendSymbols(String data, QrCode template) { + List< String > dataList = splitData(data, template); + int parity = calculateStructuredAppendParity(data, template); + return createStructuredAppendSymbols(dataList, parity, template); + } + + private static List< String > splitData(String data, QrCode template) { + + QrCode testSymbol = new QrCode() { + @Override + protected void plotSymbol() { + } // expensive plotting is not required + }; + clone(template, testSymbol); + testSymbol.setStructuredAppendTotal(2); + + List< String > split = new ArrayList<>(); + while (!data.isEmpty()) { + int low = 0; + int high = data.length(); + while (low <= high) { + int mid = (low + high) >>> 1; + String candidate = data.substring(0, mid); + if (fits(candidate, testSymbol)) { + low = mid + 1; + } else { + high = mid - 1; + } + } + if (high == 0) { + // This should never happen, as the template should be able to fit at least one character + throw new IllegalStateException("Failed to fit any data into the template symbol"); + } + split.add(data.substring(0, high)); + data = data.substring(high); + } + + if (split.size() > MAX_STRUCTURED_APPEND_SYMBOLS) { + throw new OkapiInputException("The specified template is too small to hold the data and structured append metadata " + + "or the data is too large for structured append. Maximum number of symbols is " + MAX_STRUCTURED_APPEND_SYMBOLS + " but got " + split.size()); + } + + return split; + } + + private static boolean fits(String data, QrCode testSymbol) { + if (!data.isEmpty()) { + try { + testSymbol.setContent(data); + } catch (OkapiInputException e) { + return false; + } + } + return true; + } + + private static List< QrCode > createStructuredAppendSymbols(List< String > split, int parity, QrCode template) { + int count = split.size(); + List< QrCode > symbols = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + String data = split.get(i); + QrCode symbol = new QrCode(); + symbols.add(symbol); + clone(template, symbol); + symbol.setStructuredAppendPosition(i + 1); + symbol.setStructuredAppendTotal(count); + symbol.setStructuredAppendParity(parity); + symbol.setContent(data); + } + return symbols; + } + + private static int calculateStructuredAppendParity(String content, QrCode template) { + EciMode eci = selectEciModeForParity(content, template); + if (eci == EciMode.NONE) { + throw new OkapiInputException("Unable to determine ECI mode"); + } + + int[] bytes = toBytes(content, eci.charset); + + int parity = 0; + for (int b : bytes) { + parity ^= b & 0xFF; + } + + return parity; + } + + private static EciMode selectEciModeForParity(String content, QrCode template) { + if (template.eciMode != -1) { + return EciMode.ECIS.stream().filter(e -> e.mode == template.eciMode).findFirst().orElse(EciMode.NONE); + } else { + return EciMode.chooseFor(content); + } + } + + private static void clone(QrCode template, QrCode target) { + target.setFontName(template.getFontName()); + target.setFontSize(template.getFontSize()); + target.setDataType(template.getDataType()); + target.setEmptyContentAllowed(template.getEmptyContentAllowed()); + target.setHumanReadableAlignment(template.getHumanReadableAlignment()); + target.setHumanReadableLocation(template.getHumanReadableLocation()); + target.setModuleWidth(template.getModuleWidth()); + target.setQuietZoneHorizontal(template.getQuietZoneHorizontal()); + target.setQuietZoneVertical(template.getQuietZoneVertical()); + target.setReaderInit(template.getReaderInit()); + target.setBarHeight(template.getBarHeight()); + target.setForceByteCompaction(template.getForceByteCompaction()); + target.setPreferredEccLevel(template.getPreferredEccLevel()); + if (template.getPreferredVersion() != 0) { + target.setPreferredVersion(template.getPreferredVersion()); + } + } + } diff --git a/src/test/java/uk/org/okapibarcode/backend/QrCodeAutoStructuredAppendTest.java b/src/test/java/uk/org/okapibarcode/backend/QrCodeAutoStructuredAppendTest.java new file mode 100644 index 00000000..839d46e5 --- /dev/null +++ b/src/test/java/uk/org/okapibarcode/backend/QrCodeAutoStructuredAppendTest.java @@ -0,0 +1,173 @@ +/* + * Copyright 2025 Jonas Scheiwiller + * + * 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 uk.org.okapibarcode.backend; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.LuminanceSource; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.ResultMetadataType; +import com.google.zxing.client.j2se.BufferedImageLuminanceSource; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.qrcode.QRCodeReader; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import uk.org.okapibarcode.backend.QrCode.EccLevel; +import uk.org.okapibarcode.output.Java2DRenderer; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.List; + +import static java.awt.image.BufferedImage.TYPE_BYTE_BINARY; +import static java.nio.charset.StandardCharsets.ISO_8859_1; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static uk.org.okapibarcode.graphics.Color.BLACK; +import static uk.org.okapibarcode.graphics.Color.WHITE; + +class QrCodeAutoStructuredAppendTest { + + @ParameterizedTest + @CsvSource({ + "0,0", + "1,1", + "5,1", + "6,2", + "80,16" + }) + public void testCreateStructuredAppendSymbols(int dataLength, int expectedSymbolsCount) throws Exception { + + QrCode template = new QrCode(); + template.setPreferredVersion(1); + template.setPreferredEccLevel(EccLevel.H); + template.setForceByteCompaction(true); + + byte[] bytes = bytes(dataLength); + List< QrCode > symbols = QrCode.createStructuredAppendSymbols(new String(bytes, ISO_8859_1), template); + assertions(symbols, bytes, expectedSymbolsCount); + } + + @ParameterizedTest + @CsvSource({ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ,1", + "祈れ、フリースイスよ、祈れ!,72" + }) + public void testCalculateStructuredAppendParity(String content, int expectedParity) throws Exception { + List< QrCode > symbols = QrCode.createStructuredAppendSymbols(content, new QrCode()); + for (QrCode symbol : symbols) { + assertEquals(expectedParity, symbol.getStructuredAppendParity()); + } + } + + @Test + public void testCreateStructuredAppendSymbolsWithTooSmallTemplate() throws Exception { + QrCode template = new QrCode(); + template.setPreferredVersion(1); + template.setPreferredEccLevel(EccLevel.H); + template.setForceByteCompaction(true); + + int maxBytesForStructuredAppendTemplate = 16 * 5; + + // First validate that the template can handle the maximum size + byte[] bytes = bytes(maxBytesForStructuredAppendTemplate); + List< QrCode > symbols = QrCode.createStructuredAppendSymbols(new String(bytes, ISO_8859_1), template); + assertions(symbols, bytes, 16, (qrCode, result) -> { + assertEquals(9, result.getRawBytes().length); + assertEquals(5, result.getText().getBytes(ISO_8859_1).length); + }); + + // Now re-test with maximum size + 1 byte + OkapiInputException exception = assertThrows(OkapiInputException.class, () -> { + QrCode.createStructuredAppendSymbols(new String(bytes(maxBytesForStructuredAppendTemplate + 1), ISO_8859_1), template); + }); + assertTrue(exception.getMessage().contains("The specified template is too small to hold the data and structured append metadata or the data is too large for structured append")); + } + + private byte[] bytes(int length) { + return "A".repeat(length).getBytes(ISO_8859_1); + } + + private static void assertions(List< QrCode > symbols, byte[] bytes, int expectedSymbolsCount) throws IOException, ReaderException { + assertions(symbols, bytes, expectedSymbolsCount, (qrCode, result) -> { + }); + } + + private static void assertions(List< QrCode > symbols, byte[] bytes, int expectedSymbolsCount, CustomSymbolAssertion customAssertion) throws IOException, ReaderException { + assertEquals(expectedSymbolsCount, symbols.size()); + + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + for (int i = 0; i < symbols.size(); i++) { + QrCode barcode = symbols.get(i); + assertEquals(EccLevel.H, barcode.getPreferredEccLevel()); + BufferedImage img = drawToImage(barcode); + Result result = decodeImage(img); + if (expectedSymbolsCount > 1) { + assertStructuredAppendSequence(result, i + 1, expectedSymbolsCount); + } else { + assertNoStructuredAppendMode(result); + } + customAssertion.assertSymbol(barcode, result); + + byte[] output = result.getText().getBytes(ISO_8859_1); + outputStream.write(output); + } + assertArrayEquals(bytes, outputStream.toByteArray()); + } + } + + private static void assertStructuredAppendSequence(Result result, int expectedPosition, int expectedTotal) { + int sequence = (int) result.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE); + int position = ((sequence >> 4) & 0x0F) + 1; + int total = (sequence & 0x0F) + 1; + assertEquals(expectedPosition, position); + assertEquals(expectedTotal, total); + } + + private static void assertNoStructuredAppendMode(Result result) { + assertNull(result.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)); + assertNull(result.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_PARITY)); + } + + private static BufferedImage drawToImage(QrCode barcode) { + int mag = 5; + BufferedImage img = new BufferedImage(barcode.getWidth() * mag, barcode.getHeight() * mag, TYPE_BYTE_BINARY); + Graphics2D g2d = img.createGraphics(); + Java2DRenderer renderer = new Java2DRenderer(g2d, mag, WHITE, BLACK); + renderer.render(barcode); + g2d.dispose(); + return img; + } + + private static Result decodeImage(BufferedImage img) throws ReaderException { + LuminanceSource source = new BufferedImageLuminanceSource(img); + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + QRCodeReader reader = new QRCodeReader(); + Result result = reader.decode(bitmap); + return result; + } + + private interface CustomSymbolAssertion { + void assertSymbol(QrCode symbol, Result result); + } +} diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-0-of-3.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-0-of-3.properties new file mode 100644 index 00000000..f068d36b --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-0-of-3.properties @@ -0,0 +1,9 @@ +PROPERTIES + +structuredAppendPosition=0 +structuredAppendTotal=3 +content=this + +ERROR + +Invalid QR Code structured append position: 0 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-0.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-0.properties new file mode 100644 index 00000000..2831ca2a --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-0.properties @@ -0,0 +1,9 @@ +PROPERTIES + +structuredAppendPosition=1 +structuredAppendTotal=0 +content=this + +ERROR + +Invalid QR Code structured append total: 0 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-4.png b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-4.png new file mode 100644 index 00000000..1bd7480b Binary files /dev/null and b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-4.png differ diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-4.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-4.properties new file mode 100644 index 00000000..41a2da48 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-1-of-4.properties @@ -0,0 +1,49 @@ +PROPERTIES + +structuredAppendParity=1 +structuredAppendPosition=1 +structuredAppendTotal=4 +content=ABCDEFGHIJKLMN + +LOG + +ECI Mode: 3 +ECI Charset: ISO-8859-1 +Encoding: ALPH 461 553 645 737 829 921 1013 +Codewords: 48 48 18 7 28 212 82 161 87 11 61 115 47 212 0 236 +Version: 1 +ECC Level: M +Mask 000 Penalties: 100 214 694 694 +Mask 001 Penalties: 130 346 826 EXIT +Mask 010 Penalties: 142 349 869 EXIT +Mask 011 Penalties: 143 332 852 EXIT +Mask 100 Penalties: 118 331 811 EXIT +Mask 101 Penalties: 138 366 846 EXIT +Mask 110 Penalties: 140 344 864 EXIT +Mask 111 Penalties: 129 333 813 EXIT +Mask Pattern: 000 +Blocks Merged: 123 -> 83 + +CODEWORDS + +72237 +15111221151 +1131131311311 +1131122311311 +113111311111311 +15122111151 +711111117 +0:38 +11111113241211 +13215112212 +1311511131111 +011124111126 +245121321 +081121131111 +721132311 +15121431111 +113111211123111 +11311221122121 +113111112112311 +151211361 +71132121111 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-17-of-17-1.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-17-of-17-1.properties new file mode 100644 index 00000000..02cb1882 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-17-of-17-1.properties @@ -0,0 +1,9 @@ +PROPERTIES + +structuredAppendPosition=17 +structuredAppendTotal=17 +content=this + +ERROR + +Invalid QR Code structured append position: 17 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-17-of-17-2.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-17-of-17-2.properties new file mode 100644 index 00000000..9e76b7d7 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-17-of-17-2.properties @@ -0,0 +1,9 @@ +PROPERTIES + +structuredAppendTotal=17 +structuredAppendPosition=17 +content=this + +ERROR + +Invalid QR Code structured append total: 17 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-2-of-4.png b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-2-of-4.png new file mode 100644 index 00000000..a83da4a6 Binary files /dev/null and b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-2-of-4.png differ diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-2-of-4.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-2-of-4.properties new file mode 100644 index 00000000..18ba6886 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-2-of-4.properties @@ -0,0 +1,49 @@ +PROPERTIES + +structuredAppendParity=1 +structuredAppendPosition=2 +structuredAppendTotal=4 +content=OPQRSTUVWXYZ0123 + +LOG + +ECI Mode: 3 +ECI Charset: ISO-8859-1 +Encoding: ALPH 1105 1197 1289 1381 1473 1565 1 93 +Codewords: 49 48 18 8 69 25 91 66 107 45 193 195 160 4 46 128 +Version: 1 +ECC Level: M +Mask 000 Penalties: 110 221 741 741 +Mask 001 Penalties: 145 355 875 EXIT +Mask 010 Penalties: 153 366 846 EXIT +Mask 011 Penalties: 142 358 838 EXIT +Mask 100 Penalties: 125 332 812 EXIT +Mask 101 Penalties: 150 366 846 EXIT +Mask 110 Penalties: 138 363 843 EXIT +Mask 111 Penalties: 123 303 783 EXIT +Mask Pattern: 000 +Blocks Merged: 121 -> 83 + +CODEWORDS + +73317 +15111131151 +1131151111311 +113112112111311 +113111311111311 +151412151 +711111117 +091119 +111111191211 +0111321232212 +02223216111 +013111114152 +0132112251111 +0812123211 +72411111111 +15141111141 +11311131711 +11311211121123 +113111311112211 +15121133211 +71111211411 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-3-of-4.png b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-3-of-4.png new file mode 100644 index 00000000..913a2e40 Binary files /dev/null and b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-3-of-4.png differ diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-3-of-4.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-3-of-4.properties new file mode 100644 index 00000000..90cd1942 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-3-of-4.properties @@ -0,0 +1,49 @@ +PROPERTIES + +structuredAppendParity=1 +structuredAppendPosition=3 +structuredAppendTotal=4 +content=456789ABCDEFGHIJ + +LOG + +ECI Mode: 3 +ECI Charset: ISO-8859-1 +Encoding: NUMB 456 789 ALPH 461 553 645 737 829 +Codewords: 50 48 17 1 156 140 84 129 71 53 20 168 85 194 207 64 +Version: 1 +ECC Level: M +Mask 000 Penalties: 110 227 707 707 +Mask 001 Penalties: 132 333 813 EXIT +Mask 010 Penalties: 139 319 799 EXIT +Mask 011 Penalties: 130 307 787 EXIT +Mask 100 Penalties: 119 290 810 EXIT +Mask 101 Penalties: 122 317 797 EXIT +Mask 110 Penalties: 126 339 819 EXIT +Mask 111 Penalties: 126 318 798 EXIT +Mask Pattern: 000 +Blocks Merged: 119 -> 79 + +CODEWORDS + +7211217 +15111221151 +11311711311 +1131122311311 +113111221111311 +151232151 +711111117 +0;19 +11111113331211 +021121322223 +01321121111121111 +11211131131113 +02132221611 +08123331 +7531311 +15123312111 +1131114213111 +11311213314 +1131115221111 +15122223111 +71122131111 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-4-of-4.png b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-4-of-4.png new file mode 100644 index 00000000..e38543a3 Binary files /dev/null and b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-4-of-4.png differ diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-4-of-4.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-4-of-4.properties new file mode 100644 index 00000000..a89219e8 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-4-of-4.properties @@ -0,0 +1,49 @@ +PROPERTIES + +structuredAppendParity=1 +structuredAppendPosition=4 +structuredAppendTotal=4 +content=KLMNOPQRSTUVWXYZ + +LOG + +ECI Mode: 3 +ECI Charset: ISO-8859-1 +Encoding: ALPH 921 1013 1105 1197 1289 1381 1473 1565 +Codewords: 51 48 18 8 57 151 235 20 101 109 9 172 183 7 14 128 +Version: 1 +ECC Level: M +Mask 000 Penalties: 118 277 797 797 +Mask 001 Penalties: 131 365 845 EXIT +Mask 010 Penalties: 139 352 872 EXIT +Mask 011 Penalties: 125 323 803 EXIT +Mask 100 Penalties: 118 343 823 EXIT +Mask 101 Penalties: 126 309 829 EXIT +Mask 110 Penalties: 126 321 841 EXIT +Mask 111 Penalties: 139 343 823 EXIT +Mask Pattern: 000 +Blocks Merged: 118 -> 76 + +CODEWORDS + +7211217 +15113111151 +1131133111311 +113112112111311 +113111221111311 +151412151 +711111117 +0;19 +11111112251211 +171224112 +22521321111 +2213121252 +04313151111 +083121111111 +7241421 +15141122211 +1131113131311 +1131122131311 +11311154211 +15122111331 +713321121 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-1-of-3.png b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-1-of-3.png new file mode 100644 index 00000000..a9b2b519 Binary files /dev/null and b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-1-of-3.png differ diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-1-of-3.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-1-of-3.properties new file mode 100644 index 00000000..ed2f5eb1 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-1-of-3.properties @@ -0,0 +1,49 @@ +PROPERTIES + +structuredAppendParity=72 +structuredAppendPosition=1 +structuredAppendTotal=3 +content=祈れ、フリー + +LOG + +ECI Mode: 20 +ECI Charset: Shift_JIS +Encoding: KNJI 1926 362 1 436 458 27 +Codewords: 48 36 135 20 128 99 195 5 168 0 33 180 14 80 6 192 +Version: 1 +ECC Level: M +Mask 000 Penalties: 108 225 785 785 +Mask 001 Penalties: 134 350 910 EXIT +Mask 010 Penalties: 141 354 834 EXIT +Mask 011 Penalties: 124 289 769 769 +Mask 100 Penalties: 127 334 854 EXIT +Mask 101 Penalties: 132 348 828 EXIT +Mask 110 Penalties: 128 320 800 EXIT +Mask 111 Penalties: 125 311 831 EXIT +Mask Pattern: 011 +Blocks Merged: 122 -> 86 + +CODEWORDS + +7121217 +151133151 +1131122311311 +113111311111311 +113112111211311 +151214151 +711111117 +082119 +1121314112112 +13211124123 +013121111311122 +031117131111 +016111111221111 +08121221211 +713244 +1511212212111 +1131131222121 +11311122411111 +1131114135 +15121111221111 +7123111131 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-2-of-3.png b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-2-of-3.png new file mode 100644 index 00000000..ae647432 Binary files /dev/null and b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-2-of-3.png differ diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-2-of-3.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-2-of-3.properties new file mode 100644 index 00000000..8eff3af3 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-2-of-3.properties @@ -0,0 +1,49 @@ +PROPERTIES + +structuredAppendParity=72 +structuredAppendPosition=2 +structuredAppendTotal=3 +content=スイスよ、祈 + +LOG + +ECI Mode: 20 +ECI Charset: Shift_JIS +Encoding: KNJI 408 387 408 358 1 1926 +Codewords: 49 36 135 20 128 96 204 6 12 51 1 102 0 9 225 128 +Version: 1 +ECC Level: M +Mask 000 Penalties: 104 215 735 735 +Mask 001 Penalties: 153 378 898 EXIT +Mask 010 Penalties: 124 310 830 EXIT +Mask 011 Penalties: 121 310 790 EXIT +Mask 100 Penalties: 136 340 860 EXIT +Mask 101 Penalties: 138 336 816 EXIT +Mask 110 Penalties: 131 323 803 EXIT +Mask 111 Penalties: 122 302 822 EXIT +Mask Pattern: 000 +Blocks Merged: 127 -> 88 + +CODEWORDS + +7211217 +15111113151 +113112112111311 +1131124111311 +11311111111111311 +15121112151 +711111117 +0:29 +11111113151211 +021411221111121 +012111311121114 +011123611311 +02213221121112 +0811121151 +7214313 +1514111111112 +113111133111111 +1131132315 +113111111112141 +15132311121 +7111111212111 diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-3-of-3.png b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-3-of-3.png new file mode 100644 index 00000000..2fc97212 Binary files /dev/null and b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-3-of-3.png differ diff --git a/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-3-of-3.properties b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-3-of-3.properties new file mode 100644 index 00000000..98f91914 --- /dev/null +++ b/src/test/resources/uk/org/okapibarcode/backend/qrcode/structured-append-eci-japanese-3-of-3.properties @@ -0,0 +1,49 @@ +PROPERTIES + +structuredAppendParity=72 +structuredAppendPosition=3 +structuredAppendTotal=3 +content=れ! + +LOG + +ECI Mode: 20 +ECI Charset: Shift_JIS +Encoding: KNJI 362 9 +Codewords: 50 36 135 20 128 32 181 0 36 +Version: 1 +ECC Level: H +Mask 000 Penalties: 111 243 803 803 +Mask 001 Penalties: 140 338 818 EXIT +Mask 010 Penalties: 142 358 878 EXIT +Mask 011 Penalties: 141 318 798 808 +Mask 100 Penalties: 149 392 872 EXIT +Mask 101 Penalties: 144 357 877 EXIT +Mask 110 Penalties: 129 336 816 EXIT +Mask 111 Penalties: 141 348 868 EXIT +Mask Pattern: 000 +Blocks Merged: 118 -> 80 + +CODEWORDS + +7111137 +151331151 +1131131311311 +113111113111311 +1131132211311 +15121121151 +711111117 +0:1: +0211311413121 +25115111121 +2211429 +011623112211 +32212211111112 +081211111131 +732112113 +1511213111112 +113111321211111 +11311244112 +11311141431 +151423221 +741213111