diff --git a/Source/interop/Encoding/DatamatrixEncodingOptions.cs b/Source/interop/Encoding/DatamatrixEncodingOptions.cs index a6c4b911..ceeda2b4 100644 --- a/Source/interop/Encoding/DatamatrixEncodingOptions.cs +++ b/Source/interop/Encoding/DatamatrixEncodingOptions.cs @@ -124,6 +124,16 @@ public bool ForceC40 set { wrappedDatamatrixEncodingOptions.ForceC40 = value; } } + /// + /// Specifies whether to enable Data Matrix Rectangular Extension (DMRE) symbol selection. + /// DMRE symbols are never selected unless this option is explicitly enabled. + /// + public bool EnableDMRE + { + get { return wrappedDatamatrixEncodingOptions.EnableDMRE; } + set { wrappedDatamatrixEncodingOptions.EnableDMRE = value; } + } + /// /// Specifies what character encoding to use where applicable (type {@link String}) /// diff --git a/Source/lib/EncodeHintType.cs b/Source/lib/EncodeHintType.cs index 92184cd7..5f38b6a7 100644 --- a/Source/lib/EncodeHintType.cs +++ b/Source/lib/EncodeHintType.cs @@ -214,6 +214,13 @@ public enum EncodeHintType /// DATA_MATRIX_COMPACT, + /// + /// Whether to enable Data Matrix Rectangular Extension (DMRE) symbol selection. + /// type: , or "true" or "false" + /// DMRE symbols are never selected unless this hint is explicitly enabled. + /// + DATA_MATRIX_DMRE, + /// /// Specifies whether to use compact mode for Code-128 code (type {@link Boolean}, or "true" or "false" /// This can yield slightly smaller bar codes. This option and {@link #FORCE_CODE_SET} are mutually diff --git a/Source/lib/datamatrix/DataMatrixWriter.cs b/Source/lib/datamatrix/DataMatrixWriter.cs index afffff21..71516bb1 100644 --- a/Source/lib/datamatrix/DataMatrixWriter.cs +++ b/Source/lib/datamatrix/DataMatrixWriter.cs @@ -77,6 +77,7 @@ public BitMatrix encode(String contents, BarcodeFormat format, int width, int he var noPadding = false; System.Text.Encoding encoding = null; var disableEci = false; + var allowDMRE = false; if (hints != null) { if (hints.ContainsKey(EncodeHintType.DATA_MATRIX_SHAPE)) @@ -130,6 +131,7 @@ public BitMatrix encode(String contents, BarcodeFormat format, int width, int he } encoding = IDictionaryExtensions.GetEncoding(hints, null); disableEci = IDictionaryExtensions.IsBooleanFlagSet(hints, EncodeHintType.DISABLE_ECI, disableEci); + allowDMRE = IDictionaryExtensions.IsBooleanFlagSet(hints, EncodeHintType.DATA_MATRIX_DMRE, allowDMRE); } //1. step: Data encodation @@ -144,10 +146,10 @@ public BitMatrix encode(String contents, BarcodeFormat format, int width, int he else { var hasForceC40Hint = IDictionaryExtensions.IsBooleanFlagSet(hints, EncodeHintType.FORCE_C40); - encoded = HighLevelEncoder.encodeHighLevel(contents, shape, minSize, maxSize, defaultEncodation, hasForceC40Hint, encoding, disableEci); + encoded = HighLevelEncoder.encodeHighLevel(contents, shape, minSize, maxSize, defaultEncodation, hasForceC40Hint, encoding, disableEci, allowDMRE); } - SymbolInfo symbolInfo = SymbolInfo.lookup(encoded.Length, shape, minSize, maxSize, true); + SymbolInfo symbolInfo = SymbolInfo.lookup(encoded.Length, shape, minSize, maxSize, true, allowDMRE); //2. step: ECC generation String codewords = ErrorCorrection.encodeECC200(encoded, symbolInfo); diff --git a/Source/lib/datamatrix/encoder/DatamatrixEncodingOptions.cs b/Source/lib/datamatrix/encoder/DatamatrixEncodingOptions.cs index 52188bc9..f4461a75 100644 --- a/Source/lib/datamatrix/encoder/DatamatrixEncodingOptions.cs +++ b/Source/lib/datamatrix/encoder/DatamatrixEncodingOptions.cs @@ -204,6 +204,39 @@ public bool CompactEncoding } } + /// + /// Specifies whether to enable Data Matrix Rectangular Extension (DMRE) symbol selection. + /// DMRE symbols are never selected unless this option is explicitly enabled. + /// +#if !NETSTANDARD && !NETFX_CORE && !PORTABLE && !UNITY + [CategoryAttribute("Standard"), DescriptionAttribute("Specifies whether to enable Data Matrix Rectangular Extension (DMRE) symbol selection.")] +#endif + public bool EnableDMRE + { + get + { + if (Hints.ContainsKey(EncodeHintType.DATA_MATRIX_DMRE)) + { + var boolObj = Hints[EncodeHintType.DATA_MATRIX_DMRE]; + if (boolObj != null) + return (bool)boolObj; + } + return false; + } + set + { + if (value) + { + Hints[EncodeHintType.DATA_MATRIX_DMRE] = value; + } + else + { + if (Hints.ContainsKey(EncodeHintType.DATA_MATRIX_DMRE)) + Hints.Remove(EncodeHintType.DATA_MATRIX_DMRE); + } + } + } + /// /// Forces C40 encoding for data-matrix (type {@link Boolean}, or "true" or "false") {@link String } value). This /// option and {@link #DATA_MATRIX_COMPACT} are mutually exclusive. diff --git a/Source/lib/datamatrix/encoder/EncoderContext.cs b/Source/lib/datamatrix/encoder/EncoderContext.cs index b24529ad..de1a6a74 100644 --- a/Source/lib/datamatrix/encoder/EncoderContext.cs +++ b/Source/lib/datamatrix/encoder/EncoderContext.cs @@ -26,6 +26,7 @@ internal sealed class EncoderContext private SymbolShapeHint shape; private Dimension minSize; private Dimension maxSize; + private bool allowDMRE; private readonly StringBuilder codewords; private int pos; private int newEncoding; @@ -84,6 +85,11 @@ public void setSizeConstraints(Dimension minSize, Dimension maxSize) this.maxSize = maxSize; } + public void setAllowDMRE(bool allowDMRE) + { + this.allowDMRE = allowDMRE; + } + public void setSkipAtEnd(int count) { this.skipAtEnd = count; @@ -148,7 +154,7 @@ public void updateSymbolInfo(int len) { if (this.symbolInfo == null || len > this.symbolInfo.dataCapacity) { - this.symbolInfo = SymbolInfo.lookup(len, shape, minSize, maxSize, true); + this.symbolInfo = SymbolInfo.lookup(len, shape, minSize, maxSize, true, allowDMRE); } } diff --git a/Source/lib/datamatrix/encoder/HighLevelEncoder.cs b/Source/lib/datamatrix/encoder/HighLevelEncoder.cs index 62723ee5..56ad5bc1 100644 --- a/Source/lib/datamatrix/encoder/HighLevelEncoder.cs +++ b/Source/lib/datamatrix/encoder/HighLevelEncoder.cs @@ -115,7 +115,7 @@ private static char randomize253State(int codewordPosition) /// 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, Encodation.ASCII, false, null, false); + return encodeHighLevel(msg, SymbolShapeHint.FORCE_NONE, null, null, Encodation.ASCII, false, null, false, false); } /// @@ -134,7 +134,7 @@ public static String encodeHighLevel(String msg, Dimension maxSize, int defaultEncodation) { - return encodeHighLevel(msg, shape, minSize, maxSize, defaultEncodation, false, null, false); + return encodeHighLevel(msg, shape, minSize, maxSize, defaultEncodation, false, null, false, false); } /// @@ -156,6 +156,31 @@ public static String encodeHighLevel(String msg, bool forceC40, Encoding encoding, bool disableEci) + { + return encodeHighLevel(msg, shape, minSize, maxSize, defaultEncodation, forceC40, encoding, disableEci, false); + } + + /// + /// Performs message encoding of a DataMatrix message using the algorithm described in annex P + /// of ISO/IEC 16022:2000(E). + /// + /// the message + /// requested shape. May be {@code SymbolShapeHint.FORCE_NONE},{@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}. + /// the minimum symbol size constraint or null for no constraint + /// the maximum symbol size constraint or null for no constraint + /// encoding mode to start with + /// enforce C40 encoding + /// if true, DMRE symbols may be selected + /// the encoded message (the char values range from 0 to 255) + public static String encodeHighLevel(String msg, + SymbolShapeHint shape, + Dimension minSize, + Dimension maxSize, + int defaultEncodation, + bool forceC40, + Encoding encoding, + bool disableEci, + bool allowDMRE) { //the codewords 0..255 are encoded as Unicode characters C40Encoder c40Encoder = new C40Encoder(); @@ -168,6 +193,7 @@ public static String encodeHighLevel(String msg, var context = new EncoderContext(msg, encoding, disableEci); context.setSymbolShape(shape); context.setSizeConstraints(minSize, maxSize); + context.setAllowDMRE(allowDMRE); if (msg.StartsWith(MACRO_05_HEADER) && msg.EndsWith(MACRO_TRAILER)) { diff --git a/Source/lib/datamatrix/encoder/SymbolInfo.cs b/Source/lib/datamatrix/encoder/SymbolInfo.cs index bbddb519..98c1e6ab 100644 --- a/Source/lib/datamatrix/encoder/SymbolInfo.cs +++ b/Source/lib/datamatrix/encoder/SymbolInfo.cs @@ -177,7 +177,7 @@ public static SymbolInfo lookup(int dataCodewords, bool allowRectangular, bool f private static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape, bool fail) { - return lookup(dataCodewords, shape, null, null, fail); + return lookup(dataCodewords, shape, null, null, fail, false); } /// /// @@ -193,10 +193,30 @@ public static SymbolInfo lookup(int dataCodewords, Dimension minSize, Dimension maxSize, bool fail) + { + return lookup(dataCodewords, shape, minSize, maxSize, fail, false); + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// if true, DMRE symbols may be selected + /// + public static SymbolInfo lookup(int dataCodewords, + SymbolShapeHint shape, + Dimension minSize, + Dimension maxSize, + bool fail, + bool allowDMRE) { foreach (SymbolInfo symbol in symbols) { - if (symbol.dmre) + if (symbol.dmre && !allowDMRE) // DMRE Symbols doesn't work in all cases continue; diff --git a/Source/test/src/datamatrix/DataMatrixWriterTestCase.cs b/Source/test/src/datamatrix/DataMatrixWriterTestCase.cs index 54bf8018..8d11f9d7 100644 --- a/Source/test/src/datamatrix/DataMatrixWriterTestCase.cs +++ b/Source/test/src/datamatrix/DataMatrixWriterTestCase.cs @@ -323,7 +323,7 @@ public void test0To256AsOneStringRoundTrip(string encodingStr) [TestCase('1')] [TestCase('a')] [TestCase('A')] - [TestCase('€')] + [TestCase('€')] public void testCharactersForLengthUntil256RoundTrip(char character) { var writer = new BarcodeWriter @@ -370,5 +370,116 @@ public void testCharactersForLengthUntil256RoundTrip(char character) if (errors.Count > 0) throw new AssertionException("not every content could be encoded and decoded"); } + + [Test] + public void testDefaultDoesNotSelectDMRE() + { + // 33 digits encode to ~17 codewords (16 pairs + 1 single digit). + // Without DMRE and FORCE_RECTANGLE: skips rect cap=5, cap=10, cap=16 symbols, + // then skips DMRE 8x48 (dmre=true), lands on rect cap=22 (SymbolInfo 22x18@16x10r2 -> 36x12). + var content = "123456789012345678901234567890123"; // 33 digits -> 17 codewords + var options = new DatamatrixEncodingOptions + { + SymbolShape = SymbolShapeHint.FORCE_RECTANGLE, + }; + var matrix = new DataMatrixWriter().encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, options.Hints); + Assert.That(matrix, Is.Not.Null); + // Should be a standard rectangular symbol (36x12), NOT DMRE 48x8 + Assert.That(matrix.Width, Is.EqualTo(36)); + Assert.That(matrix.Height, Is.EqualTo(12)); + } + + [Test] + public void testDMREOptInSelectsDMRESymbol() + { + // Same payload, but with DMRE enabled and FORCE_RECTANGLE + // 33 digits -> 17 codewords, fits DMRE 8x48 (cap=18), skips rect cap=5/10/16 + var content = "123456789012345678901234567890123"; // 33 digits -> 17 codewords + var options = new DatamatrixEncodingOptions + { + SymbolShape = SymbolShapeHint.FORCE_RECTANGLE, + EnableDMRE = true + }; + var matrix = new DataMatrixWriter().encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, options.Hints); + Assert.That(matrix, Is.Not.Null); + // Should select DMRE 8x48 (48x8) instead of standard 36x12 + Assert.That(matrix.Width, Is.EqualTo(48)); + Assert.That(matrix.Height, Is.EqualTo(8)); + } + + [Test] + public void testDMREExactSizeViaMinMax() + { + // Constrain to DMRE 8x48 dimensions with DMRE enabled + var content = "Hello"; + var options = new DatamatrixEncodingOptions + { + MinSize = new Dimension(48, 8), + MaxSize = new Dimension(48, 8), + EnableDMRE = true + }; + var matrix = new DataMatrixWriter().encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, options.Hints); + Assert.That(matrix, Is.Not.Null); + Assert.That(matrix.Width, Is.EqualTo(48)); + Assert.That(matrix.Height, Is.EqualTo(8)); + } + + [Test] + public void testDMREWithoutOptInFailsWithExactDMREConstraints() + { + // Same min/max constraint but without EnableDMRE - DMRE symbol is skipped + var content = "Hello"; + var options = new DatamatrixEncodingOptions + { + MinSize = new Dimension(48, 8), + MaxSize = new Dimension(48, 8), + // EnableDMRE not set - defaults to false + }; + // No standard symbol matches 48x8, so we expect an exception + Assert.Throws(() => + new DataMatrixWriter().encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, options.Hints)); + } + + [Test] + public void testDMRECapacityBoundaryFallsThroughToNextSymbol() + { + // 35 digits encode to 18 digit-pair codewords? Actually: + // 34 digits -> 17 codewords, 35 digits -> 18 codewords. + // To exceed 8x48 capacity 18, use 37 digits -> 19 codewords. + var content = "1234567890123456789012345678901234567"; // 37 digits -> 19 codewords + + var options = new DatamatrixEncodingOptions + { + EnableDMRE = true, + SymbolShape = SymbolShapeHint.FORCE_RECTANGLE + }; + + var matrix = new DataMatrixWriter().encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, options.Hints); + + Assert.That(matrix, Is.Not.Null); + Assert.That(matrix.Width, Is.Not.EqualTo(48)); + Assert.That(matrix.Height, Is.Not.EqualTo(8)); + } + + [Test] + public void testDMREOptInRoundTrip() + { + // Round-trip encode/decode with DMRE enabled using Internal.Decoder on the raw matrix + var content = "123456789012345678901234567890123"; // 33 digits -> 17 codewords + var options = new DatamatrixEncodingOptions + { + EnableDMRE = true, + SymbolShape = SymbolShapeHint.FORCE_RECTANGLE + }; + var writer = new DataMatrixWriter(); + var matrix = writer.encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, options.Hints); + Assert.That(matrix, Is.Not.Null); + Assert.That(matrix.Width, Is.EqualTo(48)); + Assert.That(matrix.Height, Is.EqualTo(8)); + + var res = new Internal.Decoder().decode(matrix); + Assert.That(res, Is.Not.Null); + Assert.That(res.Text, Is.EqualTo(content)); + } } } \ No newline at end of file