diff --git a/core/src/main/java/com/google/zxing/EncodeHintType.java b/core/src/main/java/com/google/zxing/EncodeHintType.java index 424d2b67b8..7f853d94b2 100644 --- a/core/src/main/java/com/google/zxing/EncodeHintType.java +++ b/core/src/main/java/com/google/zxing/EncodeHintType.java @@ -101,4 +101,10 @@ public enum EncodeHintType { * (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, } diff --git a/core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java b/core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java index 9532857a04..23543dd90e 100644 --- a/core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java +++ b/core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java @@ -99,6 +99,13 @@ public static QRCode encode(String content, } } + // Append the FNC1 mode header for GS1 formatted data if applicable + boolean hasGS1FormatHint = hints != null && hints.containsKey(EncodeHintType.GS1_FORMAT); + if (hasGS1FormatHint && Boolean.valueOf(hints.get(EncodeHintType.GS1_FORMAT).toString())) { + // GS1 formatted codes are prefixed with a FNC1 in first position mode header + appendModeInfo(Mode.FNC1_FIRST_POSITION, headerBits); + } + // (With ECI in place,) Write the mode marker appendModeInfo(mode, headerBits); diff --git a/core/src/test/java/com/google/zxing/qrcode/encoder/EncoderTestCase.java b/core/src/test/java/com/google/zxing/qrcode/encoder/EncoderTestCase.java index 96c785e607..d5bc6de596 100644 --- a/core/src/test/java/com/google/zxing/qrcode/encoder/EncoderTestCase.java +++ b/core/src/test/java/com/google/zxing/qrcode/encoder/EncoderTestCase.java @@ -255,6 +255,76 @@ public void testEncodeShiftjisNumeric() throws WriterException { assertEquals(expected, qrCode.toString()); } + @Test + public void testEncodeGS1WithStringTypeHint() throws WriterException { + Map hints = new EnumMap<>(EncodeHintType.class); + hints.put(EncodeHintType.GS1_FORMAT, "true"); + QRCode qrCode = Encoder.encode("100001%11171218", ErrorCorrectionLevel.H, hints); + verifyGS1EncodedData(qrCode); + } + + @Test + public void testEncodeGS1WithBooleanTypeHint() throws WriterException { + Map hints = new EnumMap<>(EncodeHintType.class); + hints.put(EncodeHintType.GS1_FORMAT, true); + QRCode qrCode = Encoder.encode("100001%11171218", ErrorCorrectionLevel.H, hints); + verifyGS1EncodedData(qrCode); + } + + @Test + public void testDoesNotEncodeGS1WhenBooleanTypeHintExplicitlyFalse() throws WriterException { + Map hints = new EnumMap<>(EncodeHintType.class); + hints.put(EncodeHintType.GS1_FORMAT, false); + QRCode qrCode = Encoder.encode("ABCDEF", ErrorCorrectionLevel.H, hints); + verifyNotGS1EncodedData(qrCode); + } + + @Test + public void testDoesNotEncodeGS1WhenStringTypeHintExplicitlyFalse() throws WriterException { + Map hints = new EnumMap<>(EncodeHintType.class); + hints.put(EncodeHintType.GS1_FORMAT, "false"); + QRCode qrCode = Encoder.encode("ABCDEF", ErrorCorrectionLevel.H, hints); + verifyNotGS1EncodedData(qrCode); + } + + @Test + public void testGS1ModeHeaderWithECI() throws WriterException { + Map hints = new EnumMap<>(EncodeHintType.class); + hints.put(EncodeHintType.CHARACTER_SET, "UTF8"); + hints.put(EncodeHintType.GS1_FORMAT, true); + QRCode qrCode = Encoder.encode("hello", ErrorCorrectionLevel.H, hints); + String expected = + "<<\n" + + " mode: BYTE\n" + + " ecLevel: H\n" + + " version: 1\n" + + " maskPattern: 5\n" + + " matrix:\n" + + " 1 1 1 1 1 1 1 0 1 0 1 1 0 0 1 1 1 1 1 1 1\n" + + " 1 0 0 0 0 0 1 0 0 1 1 0 0 0 1 0 0 0 0 0 1\n" + + " 1 0 1 1 1 0 1 0 1 1 1 0 0 0 1 0 1 1 1 0 1\n" + + " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1\n" + + " 1 0 1 1 1 0 1 0 1 0 1 0 0 0 1 0 1 1 1 0 1\n" + + " 1 0 0 0 0 0 1 0 0 1 1 1 1 0 1 0 0 0 0 0 1\n" + + " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + + " 0 0 0 0 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0 0 0\n" + + " 0 0 0 0 0 1 1 0 0 1 1 0 0 0 1 0 1 0 1 0 1\n" + + " 0 1 0 1 1 0 0 1 0 1 1 1 1 1 1 0 1 1 1 0 1\n" + + " 0 1 0 1 1 1 1 0 1 1 0 0 0 1 0 1 0 1 1 0 0\n" + + " 1 1 1 1 0 1 0 1 0 0 1 0 1 0 0 1 1 1 1 0 0\n" + + " 1 0 0 1 0 0 1 1 0 1 1 0 1 0 1 0 0 1 0 0 1\n" + + " 0 0 0 0 0 0 0 0 1 1 1 1 1 0 1 0 1 0 0 1 0\n" + + " 1 1 1 1 1 1 1 0 0 0 1 1 0 0 1 0 0 0 1 1 0\n" + + " 1 0 0 0 0 0 1 0 1 1 0 0 0 0 1 0 1 1 1 0 0\n" + + " 1 0 1 1 1 0 1 0 0 1 0 0 1 0 1 0 1 0 0 0 1\n" + + " 1 0 1 1 1 0 1 0 0 0 0 0 1 1 1 0 1 1 1 1 0\n" + + " 1 0 1 1 1 0 1 0 0 0 1 0 0 1 0 0 1 0 1 1 1\n" + + " 1 0 0 0 0 0 1 0 0 1 0 0 0 1 1 0 0 1 1 1 1\n" + + " 1 1 1 1 1 1 1 0 0 1 1 1 0 1 1 0 1 0 0 1 0\n" + + ">>\n"; + assertEquals(expected, qrCode.toString()); + } + @Test public void testAppendModeInfo() { BitArray bits = new BitArray(); @@ -594,6 +664,76 @@ public void testBugInBitVectorNumBytes() throws WriterException { Encoder.encode(builder.toString(), ErrorCorrectionLevel.L); } + private void verifyGS1EncodedData(QRCode qrCode) { + String expected = + "<<\n" + + " mode: ALPHANUMERIC\n" + + " ecLevel: H\n" + + " version: 2\n" + + " maskPattern: 4\n" + + " matrix:\n" + + " 1 1 1 1 1 1 1 0 0 1 1 1 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + + " 1 0 0 0 0 0 1 0 1 1 0 0 0 0 0 1 1 0 1 0 0 0 0 0 1\n" + + " 1 0 1 1 1 0 1 0 0 0 0 0 1 1 1 0 1 0 1 0 1 1 1 0 1\n" + + " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 1 1 0 0 1 0 1 1 1 0 1\n" + + " 1 0 1 1 1 0 1 0 0 0 1 1 1 0 0 0 1 0 1 0 1 1 1 0 1\n" + + " 1 0 0 0 0 0 1 0 1 1 0 1 1 0 1 1 0 0 1 0 0 0 0 0 1\n" + + " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + + " 0 0 0 0 0 0 0 0 1 1 0 1 1 0 1 1 0 0 0 0 0 0 0 0 0\n" + + " 0 0 0 0 1 1 1 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 0\n" + + " 0 1 1 0 1 1 0 0 1 1 1 0 0 0 1 1 1 1 1 1 1 0 0 0 1\n" + + " 0 0 1 1 1 1 1 0 1 1 1 1 1 0 1 0 0 0 0 0 0 1 1 1 0\n" + + " 1 0 1 1 1 0 0 1 1 1 0 1 1 1 1 1 0 1 1 0 1 1 1 0 0\n" + + " 0 1 0 1 0 0 1 1 1 1 1 1 0 0 1 1 0 1 0 0 0 0 0 1 0\n" + + " 1 0 0 1 1 1 0 0 1 1 0 0 0 1 1 0 1 0 1 0 1 0 0 0 0\n" + + " 0 0 1 0 0 1 1 1 0 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0\n" + + " 0 0 0 1 1 0 0 1 0 0 1 0 0 1 1 0 0 1 0 0 0 1 1 1 0\n" + + " 1 1 0 1 0 1 1 0 1 0 1 0 0 0 1 1 1 1 1 1 1 0 0 0 0\n" + + " 0 0 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 0 0 0 1 1 0 1 0\n" + + " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 0 1 0 1 0 0 0 0\n" + + " 1 0 0 0 0 0 1 0 1 1 0 0 0 1 0 1 1 0 0 0 1 0 1 1 0\n" + + " 1 0 1 1 1 0 1 0 1 1 1 0 0 0 0 0 1 1 1 1 1 1 0 0 1\n" + + " 1 0 1 1 1 0 1 0 0 0 0 0 0 1 1 1 0 0 1 1 0 1 0 0 0\n" + + " 1 0 1 1 1 0 1 0 0 0 1 1 0 1 0 1 1 1 0 1 1 0 0 1 0\n" + + " 1 0 0 0 0 0 1 0 0 1 1 0 1 1 1 1 1 0 1 0 1 1 0 0 0\n" + + " 1 1 1 1 1 1 1 0 0 0 1 0 0 0 0 1 1 0 0 1 1 0 0 1 1\n" + + ">>\n"; + assertEquals(expected, qrCode.toString()); + } + + private void verifyNotGS1EncodedData(QRCode qrCode) { + String expected = + "<<\n" + + " mode: ALPHANUMERIC\n" + + " ecLevel: H\n" + + " version: 1\n" + + " maskPattern: 4\n" + + " matrix:\n" + + " 1 1 1 1 1 1 1 0 0 1 0 1 0 0 1 1 1 1 1 1 1\n" + + " 1 0 0 0 0 0 1 0 1 0 1 0 1 0 1 0 0 0 0 0 1\n" + + " 1 0 1 1 1 0 1 0 0 0 0 0 0 0 1 0 1 1 1 0 1\n" + + " 1 0 1 1 1 0 1 0 0 1 0 0 1 0 1 0 1 1 1 0 1\n" + + " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 1 0 1 1 1 0 1\n" + + " 1 0 0 0 0 0 1 0 1 0 0 1 1 0 1 0 0 0 0 0 1\n" + + " 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1\n" + + " 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0\n" + + " 0 0 0 0 1 1 1 1 0 1 1 0 1 0 1 1 0 0 0 1 0\n" + + " 0 0 0 0 1 1 0 1 1 1 0 0 1 1 1 1 0 1 1 0 1\n" + + " 1 0 0 0 0 1 1 0 0 1 0 1 0 0 0 1 1 1 0 1 1\n" + + " 1 0 0 1 1 1 0 0 1 1 1 1 0 0 0 0 1 0 0 0 0\n" + + " 0 1 1 1 1 1 1 0 1 0 1 0 1 1 1 0 0 1 1 0 0\n" + + " 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 0 0 0 1 0 1\n" + + " 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 0 0 1 1 0 0\n" + + " 1 0 0 0 0 0 1 0 1 1 0 1 0 0 0 1 0 1 1 1 1\n" + + " 1 0 1 1 1 0 1 0 1 0 0 1 0 0 0 1 1 0 0 1 1\n" + + " 1 0 1 1 1 0 1 0 0 0 1 1 0 1 0 0 0 0 1 1 1\n" + + " 1 0 1 1 1 0 1 0 0 1 0 1 0 0 0 1 1 0 0 0 0\n" + + " 1 0 0 0 0 0 1 0 0 1 0 0 1 0 0 1 1 0 0 0 1\n" + + " 1 1 1 1 1 1 1 0 0 0 1 0 0 1 0 0 0 0 1 1 1\n" + + ">>\n"; + assertEquals(expected, qrCode.toString()); + } + private static String shiftJISString(byte[] bytes) throws WriterException { try { return new String(bytes, "Shift_JIS");