diff --git a/.gitignore b/.gitignore index edccab16..cf797c57 100644 --- a/.gitignore +++ b/.gitignore @@ -251,3 +251,6 @@ paket-files/ .idea/ *.sln.iml /SGHLabel + +# CodeRush +.cr/ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Label.UnitTest/BinaryKits.Zpl.Label.UnitTest.csproj b/src/BinaryKits.Zpl.Label.UnitTest/BinaryKits.Zpl.Label.UnitTest.csproj index 9e99d4c6..42897ddc 100644 --- a/src/BinaryKits.Zpl.Label.UnitTest/BinaryKits.Zpl.Label.UnitTest.csproj +++ b/src/BinaryKits.Zpl.Label.UnitTest/BinaryKits.Zpl.Label.UnitTest.csproj @@ -21,7 +21,13 @@ - + + PreserveNewest + + + PreserveNewest + + PreserveNewest diff --git a/src/BinaryKits.Zpl.Label.UnitTest/DownloadTest.cs b/src/BinaryKits.Zpl.Label.UnitTest/DownloadTest.cs index 2c792026..32efeb55 100644 --- a/src/BinaryKits.Zpl.Label.UnitTest/DownloadTest.cs +++ b/src/BinaryKits.Zpl.Label.UnitTest/DownloadTest.cs @@ -12,15 +12,15 @@ public class DownloadTest { [TestMethod] [DeploymentItem(@"ZplData/Zpl.png")] - [DeploymentItem(@"ZplData/DownloadGraphics.txt")] - public void DownloadGraphics() + [DeploymentItem(@"ZplData/DownloadGraphicsACS.txt")] + public void DownloadGraphicsACS() { var imageData = File.ReadAllBytes("Zpl.png"); var elements = new List { new ZplGraphicBox(0, 0, 100, 100, 4), - new ZplDownloadGraphics('R', "SAMPLE", imageData), + new ZplDownloadGraphics('R', "SAMPLE", imageData,ZplCompressionScheme.ACS), new ZplRecallGraphic(100, 100, 'R', "SAMPLE") }; @@ -35,7 +35,63 @@ public void DownloadGraphics() Debug.WriteLine(output); Assert.IsNotNull(output); - var zplData = File.ReadAllText("DownloadGraphics.txt"); + var zplData = File.ReadAllText("DownloadGraphicsACS.txt"); + Assert.AreEqual(zplData, output); + } + [TestMethod] + [DeploymentItem(@"ZplData/Zpl.png")] + [DeploymentItem(@"ZplData/DownloadGraphicsZ64.txt")] + public void DownloadGraphicsZ64() + { + var imageData = File.ReadAllBytes("Zpl.png"); + + var elements = new List + { + new ZplGraphicBox(0, 0, 100, 100, 4), + new ZplDownloadGraphics('R', "SAMPLE", imageData,ZplCompressionScheme.Z64), + new ZplRecallGraphic(100, 100, 'R', "SAMPLE") + }; + + var renderEngine = new ZplEngine(elements); + var output = renderEngine.ToZplString(new ZplRenderOptions + { + AddEmptyLineBeforeElementStart = true, + TargetPrintDpi = 200, + SourcePrintDpi = 200 + }); + + Debug.WriteLine(output); + Assert.IsNotNull(output); + + var zplData = File.ReadAllText("DownloadGraphicsZ64.txt"); + Assert.AreEqual(zplData, output); + } + [TestMethod] + [DeploymentItem(@"ZplData/Zpl.png")] + [DeploymentItem(@"ZplData/DownloadGraphicsB64.txt")] + public void DownloadGraphicsB64() + { + var imageData = File.ReadAllBytes("Zpl.png"); + + var elements = new List + { + new ZplGraphicBox(0, 0, 100, 100, 4), + new ZplDownloadGraphics('R', "SAMPLE", imageData,ZplCompressionScheme.B64), + new ZplRecallGraphic(100, 100, 'R', "SAMPLE") + }; + + var renderEngine = new ZplEngine(elements); + var output = renderEngine.ToZplString(new ZplRenderOptions + { + AddEmptyLineBeforeElementStart = true, + TargetPrintDpi = 200, + SourcePrintDpi = 200 + }); + + Debug.WriteLine(output); + Assert.IsNotNull(output); + + var zplData = File.ReadAllText("DownloadGraphicsB64.txt"); Assert.AreEqual(zplData, output); } diff --git a/src/BinaryKits.Zpl.Label.UnitTest/ZebraHexCompressionHelperTest.cs b/src/BinaryKits.Zpl.Label.UnitTest/ZebraACSCompressionHelperTest.cs similarity index 82% rename from src/BinaryKits.Zpl.Label.UnitTest/ZebraHexCompressionHelperTest.cs rename to src/BinaryKits.Zpl.Label.UnitTest/ZebraACSCompressionHelperTest.cs index 05df69a3..5f5721ee 100644 --- a/src/BinaryKits.Zpl.Label.UnitTest/ZebraHexCompressionHelperTest.cs +++ b/src/BinaryKits.Zpl.Label.UnitTest/ZebraACSCompressionHelperTest.cs @@ -4,10 +4,10 @@ namespace BinaryKits.Zpl.Label.UnitTest { [TestClass] - public class ZebraHexCompressionHelperTest + public class ZebraACSCompressionHelperTest { [TestMethod] - [DataRow(1, "G")] + //[DataRow(1, "G")] [DataRow(2, "H")] [DataRow(3, "I")] [DataRow(4, "J")] @@ -48,7 +48,7 @@ public class ZebraHexCompressionHelperTest [DataRow(400, "z")] public void GetZebraCharCount_Simple_Successful(int charRepeatCount, string compressed) { - var zebraCharCount = ZebraHexCompressionHelper.GetZebraCharCount(charRepeatCount); + var zebraCharCount = ZebraACSCompressionHelper.GetZebraCharCount(charRepeatCount); Assert.AreEqual(compressed, zebraCharCount); } @@ -70,15 +70,15 @@ public void GetZebraCharCount_Simple_Successful(int charRepeatCount, string comp [DataRow(842, "zzhH")] public void GetZebraCharCount_Complex_Successful(int charRepeatCount, string compressed) { - var zebraCharCount = ZebraHexCompressionHelper.GetZebraCharCount(charRepeatCount); + var zebraCharCount = ZebraACSCompressionHelper.GetZebraCharCount(charRepeatCount); Assert.AreEqual(compressed, zebraCharCount); } [TestMethod] public void Compress_ValidData1_Successful() { - var compressed = ZebraHexCompressionHelper.Compress("FFFF\nF00F\nFFFF\n", 2); - Assert.AreEqual("JFGFH0GFJF", compressed); + var compressed = ZebraACSCompressionHelper.Compress("FFFF\nF00F\nFFFF\n", 2); + Assert.AreEqual("JFFH0FJF", compressed); //^XA //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF //^FO20,20 @@ -89,8 +89,8 @@ public void Compress_ValidData1_Successful() [TestMethod] public void Compress_ValidData2_Successful() { - var compressed = ZebraHexCompressionHelper.Compress("FFFFF00FFFFF", 2); - Assert.AreEqual("JFGFH0GFJF", compressed); + var compressed = ZebraACSCompressionHelper.Compress("FFFFF00FFFFF", 2); + Assert.AreEqual("JFFH0FJF", compressed); //^XA //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF //^FO20,20 @@ -101,8 +101,8 @@ public void Compress_ValidData2_Successful() [TestMethod] public void Compress_ValidData3_Successful() { - var compressed = ZebraHexCompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFFFFFFFFFFFFFFFFFF\n", 10); - Assert.AreEqual("gFG8I0JFJ0JFI0!::JFJ0JFJ0JF::gF", compressed); + var compressed = ZebraACSCompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFFFFFFFFFFFFFFFFFF\n", 10); + Assert.AreEqual("gF8I0JFJ0JFI0!::JFJ0JFJ0JF::gF", compressed); //^XA //~DGR:SAMPLE.GRF,00080,010,gFG8I0JFJ0JFI0!::JFJ0JFJ0JF::gF //^FO20,20 @@ -115,8 +115,8 @@ public void CompressUncompress_Flow_Successful() { var originalData = "FFFFFFFFFFFFFFFFFFFF\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFFFFFFFFFFFFFFFFFF\n"; - var compressed = ZebraHexCompressionHelper.Compress(originalData, 10); - var uncompressed = ZebraHexCompressionHelper.Uncompress(compressed, 10); + var compressed = ZebraACSCompressionHelper.Compress(originalData, 10); + var uncompressed = ZebraACSCompressionHelper.Uncompress(compressed, 10); Assert.AreEqual(originalData, uncompressed); } @@ -125,7 +125,7 @@ public void Uncompress_ValidData1_Successful() { var compressedData = "gO0E\n"; - var uncompressed = ZebraHexCompressionHelper.Uncompress(compressedData, 10); + var uncompressed = ZebraACSCompressionHelper.Uncompress(compressedData, 10); Assert.AreEqual("00000000000000000000000000000E\n", uncompressed); } @@ -134,7 +134,7 @@ public void Uncompress_ValidData2_Successful() { var compressedData = "gO0GE\n"; - var uncompressed = ZebraHexCompressionHelper.Uncompress(compressedData, 10); + var uncompressed = ZebraACSCompressionHelper.Uncompress(compressedData, 10); Assert.AreEqual("00000000000000000000000000000E\n", uncompressed); } } diff --git a/src/BinaryKits.Zpl.Label.UnitTest/ZebraB64CompressionHelperTest.cs b/src/BinaryKits.Zpl.Label.UnitTest/ZebraB64CompressionHelperTest.cs new file mode 100644 index 00000000..c2b9cafd --- /dev/null +++ b/src/BinaryKits.Zpl.Label.UnitTest/ZebraB64CompressionHelperTest.cs @@ -0,0 +1,31 @@ +using BinaryKits.Zpl.Label.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace BinaryKits.Zpl.Label.UnitTest +{ + [TestClass] + public class ZebraB64CompressionHelperTest + { + [TestMethod] + public void Compress_ValidData1_Successful() + { + var compressed = ZebraB64CompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"); + Assert.AreEqual(":B64://///////////4AA//8AAP//AAGAAP//AAD//wABgAD//wAA//8AAf//AAD//wAA/////wAA//8AAP////8AAP//AAD///////////////8=:e4b3", compressed); + //^XA + //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF + //^FO20,20 + //^XGR:SAMPLE.GRF,1,1^FS + //^XZ + } + [TestMethod] + public void CompressUncompress_Flow_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + string compressed = ZebraB64CompressionHelper.Compress(originalData); + string uncompressed = ZebraB64CompressionHelper.Uncompress(compressed).ToHexFromBytes(); + Assert.AreEqual(originalData, uncompressed); + } + } +} diff --git a/src/BinaryKits.Zpl.Label.UnitTest/ZebraZ64CompressionHelperTest.cs b/src/BinaryKits.Zpl.Label.UnitTest/ZebraZ64CompressionHelperTest.cs new file mode 100644 index 00000000..92b742e2 --- /dev/null +++ b/src/BinaryKits.Zpl.Label.UnitTest/ZebraZ64CompressionHelperTest.cs @@ -0,0 +1,88 @@ +using BinaryKits.Zpl.Label.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO.Compression; + +namespace BinaryKits.Zpl.Label.UnitTest +{ + [TestClass] + public class ZebraZ64CompressionHelperTest + { + [TestMethod] + public void Compress_ValidData1_Successful() + { + var compressed = ZebraZ64CompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"); + Assert.AreEqual(":Z64:eNr7/x8GGhj+/2cAYUZsLCjNAFKJjQUDAOLDM1I=:8f39", compressed); + //^XA + //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF + //^FO20,20 + //^XGR:SAMPLE.GRF,1,1^FS + //^XZ + } + [TestMethod] + public void CompressUncompress_Flow_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + string compressed = ZebraZ64CompressionHelper.Compress(originalData); + string uncompressed = ZebraZ64CompressionHelper.Uncompress(compressed).ToHexFromBytes(); + Assert.AreEqual(originalData, uncompressed); + } +#if NET5_0_OR_GREATER + [TestMethod] + public void DeflateNetStandardSameAsNetCore_Optimal_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.Optimal; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void DeflateNetStandardSameAsNetCore_Fastest_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.Fastest; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void DeflateNetStandardSameAsNetCore_SmallestSize_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.SmallestSize; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void DeflateNetStandardSameAsNetCore_NoCompression_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.NoCompression; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void InflateNetStandardSameAsNetCore_Successful() + { + var originalData = "eJz7/x8GGhj+/2cAYUZsLCgNxNhZMAAA4sMzUg=="; + + string compressed = ZebraZ64CompressionHelper.Inflate(originalData.FromBase64()).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.InflateCore(originalData.FromBase64()).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } +#endif + } +} diff --git a/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphics.txt b/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphics.txt deleted file mode 100644 index 5835221f..00000000 --- a/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphics.txt +++ /dev/null @@ -1,13 +0,0 @@ -^XA -^LH0,0 -^CI28 - -^FO0,0 -^GB100,100,4,B,0^FS - -~DGR:SAMPLE.GRF,1190,17, -,:::::::::H0G3MFG8G0G3MFG8G0G3HFG8,H0G2M0G8G0G3M0G8G0G2H0G8,H0G2L0G1H0G2G8L0G8G0G2H0G8,H0G2L0G1H0G2G4L0G8G0G2H0G8,H0G2L0G2H0H2L0G8G0G2H0G8,H0G2L0G2H0G2G1L0G8G0G2H0G8,H0G2L0G4H0G2G0G8K0G8G0G2H0G8,H0G2L0G4H0G2G0G4K0G8G0G2H0G8,H0G2L0G8H0G2G0G2K0G8G0G2H0G8,H0G2L0G8H0G2G0G1K0G8G0G2H0G8,H0G3IFGCG0G1I0G2H0HFGEH0G8G0G2H0G8,L0G4G0G1I0G2H0G8G0G2H0G8G0G2H0G8,L0G8G0G2I0G2H0G8G0G2H0G8G0G2H0G8,:K0G1H0G4I0G2H0G8G0G2H0G8G0G2H0G8,:K0G2H0G8I0G2H0G8G0G2H0G8G0G2H0G8,:K0G4G0G1J0G2H0G8G0G2H0G8G0G2H0G8,:K0G8G0G2J0G2H0HFGEH0G8G0G2H0G8,K0G8G0G2J0G2M0G8G0G2H0G8,J0G1H0G4J0G2M0G8G0G2H0G8,:J0G2H0G8J0G2M0G8G0G2H0G8,:J0G4G0G1K0G2M0G8G0G2H0G8,:J0G8G0G2K0G2M0G8G0G2H0G8,:I0G1H0G4K0G2H0KFG8G0G2H0G8,I0G1H0G4K0G2H0G8L0G2H0G8,I0G2H0G8K0G2H0G8L0G2H0G8,:I0G4G0G1L0G2H0G8L0G2H0G8,:I0G8G0G2L0G2H0G8L0G2H0G8,:H0G1H0G4L0G2H0G8L0G2H0G8,:H0G2H0KFG8G0G2H0G8L0G2H0KFG8,H0G2M0G8G0G2H0G8L0G2M0G8,::::::::H0G3MFG8G0G3HFG8L0G3MFG8,,:::::::: - -^FO100,100 -^XGR:SAMPLE.GRF,1,1^FS -^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsACS.txt b/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsACS.txt new file mode 100644 index 00000000..1e7559d4 --- /dev/null +++ b/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsACS.txt @@ -0,0 +1,13 @@ +^XA +^LH0,0 +^CI28 + +^FO0,0 +^GB100,100,4,B,0^FS + +~DGR:SAMPLE.GRF,1190,17, +,:::::::::H03MF803MF803HF8,H02M0803M0802H08,H02L01H028L0802H08,H02L01H024L0802H08,H02L02H0H2L0802H08,H02L02H021L0802H08,H02L04H0208K0802H08,H02L04H0204K0802H08,H02L08H0202K0802H08,H02L08H0201K0802H08,H03IFC01I02H0HFEH0802H08,L0401I02H0802H0802H08,L0802I02H0802H0802H08,:K01H04I02H0802H0802H08,:K02H08I02H0802H0802H08,:K0401J02H0802H0802H08,:K0802J02H0HFEH0802H08,K0802J02M0802H08,J01H04J02M0802H08,:J02H08J02M0802H08,:J0401K02M0802H08,:J0802K02M0802H08,:I01H04K02H0KF802H08,I01H04K02H08L02H08,I02H08K02H08L02H08,:I0401L02H08L02H08,:I0802L02H08L02H08,:H01H04L02H08L02H08,:H02H0KF802H08L02H0KF8,H02M0802H08L02M08,::::::::H03MF803HF8L03MF8,,:::::::: + +^FO100,100 +^XGR:SAMPLE.GRF,1,1^FS +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsB64.txt b/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsB64.txt new file mode 100644 index 00000000..b3c7f2e3 --- /dev/null +++ b/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsB64.txt @@ -0,0 +1,13 @@ +^XA +^LH0,0 +^CI28 + +^FO0,0 +^GB100,100,4,B,0^FS + +~DGR:SAMPLE.GRF,1190,17, +:B64:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////4A/////gD/4AAAAAAAgAAAAgDAAAACAIAgAAAAAACAAAAEAKAAAAIAgCAAAAAAAIAAAAQAkAAAAgCAIAAAAAAAgAAACACIAAACAIAgAAAAAACAAAAIAIQAAAIAgCAAAAAAAIAAABAAggAAAgCAIAAAAAAAgAAAEACBAAACAIAgAAAAAACAAAAgAICAAAIAgCAAAAAAAIAAACAAgEAAAgCAIAAAAAAA//8AQACAP/gCAIAgAAAAAAAAAQBAAIAgCAIAgCAAAAAAAAACAIAAgCAIAgCAIAAAAAAAAAIAgACAIAgCAIAgAAAAAAAABAEAAIAgCAIAgCAAAAAAAAAEAQAAgCAIAgCAIAAAAAAAAAgCAACAIAgCAIAgAAAAAAAACAIAAIAgCAIAgCAAAAAAAAAQBAAAgCAIAgCAIAAAAAAAABAEAACAIAgCAIAgAAAAAAAAIAgAAIA/+AIAgCAAAAAAAAAgCAAAgAAAAgCAIAAAAAAAAEAQAACAAAACAIAgAAAAAAAAQBAAAIAAAAIAgCAAAAAAAACAIAAAgAAAAgCAIAAAAAAAAIAgAACAAAACAIAgAAAAAAABAEAAAIAAAAIAgCAAAAAAAAEAQAAAgAAAAgCAIAAAAAAAAgCAAACAAAACAIAgAAAAAAACAIAAAIAAAAIAgCAAAAAAAAQBAAAAgD///gCAIAAAAAAABAEAAACAIAAAAIAgAAAAAAAIAgAAAIAgAAAAgCAAAAAAAAgCAAAAgCAAAACAIAAAAAAAEAQAAACAIAAAAIAgAAAAAAAQBAAAAIAgAAAAgCAAAAAAACAIAAAAgCAAAACAIAAAAAAAIAgAAACAIAAAAIAgAAAAAABAEAAAAIAgAAAAgCAAAAAAAEAQAAAAgCAAAACAIAAAAAAAgD///gCAIAAAAIA///4AAACAAAACAIAgAAAAgAAAAgAAAIAAAAIAgCAAAACAAAACAAAAgAAAAgCAIAAAAIAAAAIAAACAAAACAIAgAAAAgAAAAgAAAIAAAAIAgCAAAACAAAACAAAAgAAAAgCAIAAAAIAAAAIAAACAAAACAIAgAAAAgAAAAgAAAIAAAAIAgCAAAACAAAACAAAAgAAAAgCAIAAAAIAAAAIAAAD////+AP/gAAAA/////gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=:9723 + +^FO100,100 +^XGR:SAMPLE.GRF,1,1^FS +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsZ64.txt b/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsZ64.txt new file mode 100644 index 00000000..b28ad728 --- /dev/null +++ b/src/BinaryKits.Zpl.Label.UnitTest/ZplData/DownloadGraphicsZ64.txt @@ -0,0 +1,13 @@ +^XA +^LH0,0 +^CI28 + +^FO0,0 +^GB100,100,4,B,0^FS + +~DGR:SAMPLE.GRF,1190,17, +:Z64:eNrV0z0OwyAMBeBHxMBWjuBUHTr2BuFoHK3H6hRq07T8DPaaRoI8fUv8ggD+5tlKKfmzvSoQr/yQjcIXHO4z3EZYcJ1hHcGD8gxpgACiGWKDrTwj6LL/AEgMYekgkwEOyQCOBngHAziPk1bo/geit0DeOnB7A7i9DtKeJ+XTHyAcnzva6yDtdZD2Okh7HdqQNfU1ajoPtMtU03mv/huIbE59:1804 + +^FO100,100 +^XGR:SAMPLE.GRF,1,1^FS +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Label/Elements/ZplCompressionScheme.cs b/src/BinaryKits.Zpl.Label/Elements/ZplCompressionScheme.cs new file mode 100644 index 00000000..cfde12ac --- /dev/null +++ b/src/BinaryKits.Zpl.Label/Elements/ZplCompressionScheme.cs @@ -0,0 +1,27 @@ +using System; + +namespace BinaryKits.Zpl.Label.Elements +{ + /// + /// Zebra Download Compression Scheme + /// + public enum ZplCompressionScheme + { + /// + /// No Compression + /// + None, + /// + /// Alternative Compression Scheme + /// + ACS, + /// + /// Z64 encoding compressed using LZ77 algorithm and encoded in Base64. + /// + Z64, + /// + /// Base64 encoding + /// + B64 + } +} diff --git a/src/BinaryKits.Zpl.Label/Elements/ZplDownloadGraphics.cs b/src/BinaryKits.Zpl.Label/Elements/ZplDownloadGraphics.cs index 46c8b509..6c15a705 100644 --- a/src/BinaryKits.Zpl.Label/Elements/ZplDownloadGraphics.cs +++ b/src/BinaryKits.Zpl.Label/Elements/ZplDownloadGraphics.cs @@ -29,8 +29,8 @@ public class ZplDownloadGraphics : ZplDownload private string _extension { get; set; } public byte[] ImageData { get; private set; } - private readonly bool _isCompressionActive; private readonly IImageConverter _imageConverter; + readonly ZplCompressionScheme _compressionScheme; /// /// Zpl Download Graphics @@ -38,13 +38,13 @@ public class ZplDownloadGraphics : ZplDownload /// /// /// - /// /// + /// public ZplDownloadGraphics( char storageDevice, string imageName, byte[] imageData, - bool isCompressionActive = true, + ZplCompressionScheme compressionScheme = ZplCompressionScheme.ACS, IImageConverter imageConverter = default) : base(storageDevice) { @@ -62,9 +62,8 @@ public ZplDownloadGraphics( { imageConverter = new ImageSharpImageConverter(); } - - _isCompressionActive = isCompressionActive; _imageConverter = imageConverter; + _compressionScheme = compressionScheme; } /// @@ -89,16 +88,31 @@ public override IEnumerable Render(ZplRenderOptions context) } var imageResult = _imageConverter.ConvertImage(objectData); + string zplData = string.Empty; - if (_isCompressionActive) + switch (_compressionScheme) { - imageResult.ZplData = ZebraHexCompressionHelper.Compress(imageResult.ZplData, imageResult.BytesPerRow); + case ZplCompressionScheme.None: + zplData = imageResult.RawData.ToHexFromBytes(); + break; + case ZplCompressionScheme.ACS: + zplData = ZebraACSCompressionHelper.Compress(imageResult.RawData.ToHexFromBytes(), imageResult.BytesPerRow); + break; + case ZplCompressionScheme.Z64: + //TODO: Reduce multiple conversions of byte array to string. + zplData = ZebraZ64CompressionHelper.Compress(imageResult.RawData); + break; + case ZplCompressionScheme.B64: + //TODO: Implement this compression scheme. + zplData = ZebraB64CompressionHelper.Compress(imageResult.RawData); + break; + //throw new NotSupportedException(); } return new List { $"~DG{StorageDevice}:{ImageName}.{_extension},{imageResult.BinaryByteCount},{imageResult.BytesPerRow},", - imageResult.ZplData + zplData }; } } diff --git a/src/BinaryKits.Zpl.Label/Elements/ZplDownloadObjects.cs b/src/BinaryKits.Zpl.Label/Elements/ZplDownloadObjects.cs index 68efb155..c80142e2 100644 --- a/src/BinaryKits.Zpl.Label/Elements/ZplDownloadObjects.cs +++ b/src/BinaryKits.Zpl.Label/Elements/ZplDownloadObjects.cs @@ -1,4 +1,5 @@ -using SixLabors.ImageSharp; +using BinaryKits.Zpl.Label.Helpers; +using SixLabors.ImageSharp; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Processing; using System; @@ -58,18 +59,14 @@ public override IEnumerable Render(ZplRenderOptions context) } } - var sb = new StringBuilder(); - foreach (byte b in objectData) - { - sb.Append(string.Format("{0:X}", b).PadLeft(2, '0')); - } + var hexString = ByteHelper.BytesToHex(objectData); var formatDownloadedInDataField = 'P'; //portable network graphic (.PNG) - ZB64 encoded var extensionOfStoredFile = 'P'; //store as compressed (.PNG) var result = new List { - $"~DY{StorageDevice}:{ObjectName},{formatDownloadedInDataField},{extensionOfStoredFile},{objectData.Length},,{sb}" + $"~DY{StorageDevice}:{ObjectName},{formatDownloadedInDataField},{extensionOfStoredFile},{objectData.Length},,{hexString}" }; return result; diff --git a/src/BinaryKits.Zpl.Viewer/Helpers/ByteHelper.cs b/src/BinaryKits.Zpl.Label/Helpers/ByteHelper.cs similarity index 51% rename from src/BinaryKits.Zpl.Viewer/Helpers/ByteHelper.cs rename to src/BinaryKits.Zpl.Label/Helpers/ByteHelper.cs index c058200e..1e56e548 100644 --- a/src/BinaryKits.Zpl.Viewer/Helpers/ByteHelper.cs +++ b/src/BinaryKits.Zpl.Label/Helpers/ByteHelper.cs @@ -1,17 +1,32 @@ using System; +using System.Linq; +using System.Text; -namespace BinaryKits.Zpl.Viewer.Helpers +namespace BinaryKits.Zpl.Label.Helpers { public static class ByteHelper { + public static string ToBase64(this byte[] bytes) { return Convert.ToBase64String(bytes); } + + public static byte[] FromBase64(this string base64) { return Convert.FromBase64String(base64); } + + public static string ToHexFromBytes(this byte[] bytes) { return BytesToHex(bytes); } + + public static byte[] ToBytesFromHex(this string hex) { return HexToBytes(hex); } + + public static byte[] EncodeBytes(this string hex) { return Encoding.ASCII.GetBytes(hex); } + public static byte[] HexToBytes(string hex) { - if (hex.IndexOfAny(new[] {'\r', '\n'} ) != -1) + if (hex.IndexOfAny(new[] { '\r', '\n' }) != -1) { hex = hex.Replace("\n", string.Empty); hex = hex.Replace("\r", string.Empty); } - + //Not Available on .NET standard and has much greater performance due to Vectorization. +#if NET5_0_OR_GREATER + return Convert.FromHexString(hex); +#else if (hex.Length % 2 == 1) { throw new Exception("The binary key cannot have an odd number of digits"); @@ -20,37 +35,42 @@ public static byte[] HexToBytes(string hex) var array = new byte[hex.Length >> 1]; for (var i = 0; i < hex.Length >> 1; ++i) { - array[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + (GetHexVal(hex[(i << 1) + 1]))); + array[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + GetHexVal(hex[(i << 1) + 1])); } return array; +#endif } public static string BytesToHex(byte[] bytes) { + //Not Available on .NET standard and has much greater performance due to Vectorization. +#if NET5_0_OR_GREATER + return Convert.ToHexString(bytes); +#else var c = new char[bytes.Length * 2]; int b; - for (var i = 0; i < bytes.Length; i++) { b = bytes[i] >> 4; - c[i * 2] = (char)(55 + b + (((b - 10) >> 31) & -7)); + c[i * 2] = (char)(55 + b + (b - 10 >> 31 & -7)); b = bytes[i] & 0xF; - c[i * 2 + 1] = (char)(55 + b + (((b - 10) >> 31) & -7)); + c[i * 2 + 1] = (char)(55 + b + (b - 10 >> 31 & -7)); } return new string(c); +#endif } private static int GetHexVal(char hex) { - int val = (int)hex; + int val = hex; //For uppercase A-F letters: //return val - (val < 58 ? 48 : 55); //For lowercase a-f letters: //return val - (val < 58 ? 48 : 87); //Or the two combined, but a bit slower: - return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); + return val - (val < 58 ? 48 : val < 97 ? 55 : 87); } } } diff --git a/src/BinaryKits.Zpl.Label/Helpers/Crc16.cs b/src/BinaryKits.Zpl.Label/Helpers/Crc16.cs new file mode 100644 index 00000000..c08a019d --- /dev/null +++ b/src/BinaryKits.Zpl.Label/Helpers/Crc16.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; + +namespace BinaryKits.Zpl.Label.Helpers +{ + /// + /// Calculate Crc16 Checksum. + /// + internal static class Crc16 + { + private static ushort[] crc16_table = { + 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, + 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF + }; + + public static ushort Compute(byte[] bytes) + { + int crc = 0; + foreach (byte b in bytes) + { + crc ^= (b << 8); + crc = (crc << 4) ^ crc16_table[(crc >> 12) & 0xF]; + crc = (crc << 4) ^ crc16_table[(crc >> 12) & 0xF]; + } + + return (ushort)(crc & 0xFFFF); + } + + public static string ComputeHex(byte[] bytes) + { + return Compute(bytes).ToString("x4"); + } + + } +} diff --git a/src/BinaryKits.Zpl.Label/Helpers/ZebraHexCompressionHelper.cs b/src/BinaryKits.Zpl.Label/Helpers/ZebraACSCompressionHelper.cs similarity index 93% rename from src/BinaryKits.Zpl.Label/Helpers/ZebraHexCompressionHelper.cs rename to src/BinaryKits.Zpl.Label/Helpers/ZebraACSCompressionHelper.cs index 86cd29cc..8f4c0d49 100644 --- a/src/BinaryKits.Zpl.Label/Helpers/ZebraHexCompressionHelper.cs +++ b/src/BinaryKits.Zpl.Label/Helpers/ZebraACSCompressionHelper.cs @@ -6,12 +6,12 @@ namespace BinaryKits.Zpl.Label.Helpers { /// - /// Alternative Data Compression Scheme for ~DG and ~DB Commands + /// Alternative Data Compression Scheme (ACS) for ~DG and ~DB Commands /// There is an alternative data compression scheme recognized by the Zebra printer. This scheme further /// reduces the actual number of data bytes and the amount of time required to download graphic images and /// bitmapped fonts with the ~DG and ~DB commands /// - public static class ZebraHexCompressionHelper + public static class ZebraACSCompressionHelper { /// /// MinCompressionBlockCount (CompressionCountMapping -> g) @@ -93,8 +93,8 @@ public static string Compress(string hexData, int bytesPerRow) compressedCurrentLine.Append(GetZebraCharCount(charRepeatCount)); compressedCurrentLine.Append(lastChar); } - - if (compressedCurrentLine.Equals(compressedPreviousLine)) + //StringBuilder to string comparision will return false on .NET Framework. + if (compressedCurrentLine.ToString().Equals(compressedPreviousLine)) { //previous line is repeated compressedLines.Append(':'); @@ -181,6 +181,11 @@ public static string Uncompress(string compressedHexData, int bytesPerRow) public static string GetZebraCharCount(int charRepeatCount) { + //only append if the repeated character is than 1 otherwise the compression scheme uses 2 characters to represent 1 + if (charRepeatCount == 1) + { + return string.Empty; + } if (CompressionCountMapping.TryGetValue(charRepeatCount, out var compressionKey)) { return $"{compressionKey}"; diff --git a/src/BinaryKits.Zpl.Label/Helpers/ZebraB64CompressionHelper.cs b/src/BinaryKits.Zpl.Label/Helpers/ZebraB64CompressionHelper.cs new file mode 100644 index 00000000..dcb07345 --- /dev/null +++ b/src/BinaryKits.Zpl.Label/Helpers/ZebraB64CompressionHelper.cs @@ -0,0 +1,42 @@ +using System; +using System.Text.RegularExpressions; + +namespace BinaryKits.Zpl.Label.Helpers +{ + /// + /// B64 Data Compression Scheme for ~DG and ~DB Commands Raw data is encoded using Base64 A CRC is calculated across + /// the Base64-encoded data. If the CRC-check fails or the download is aborted, the object can be invalidated by the + /// printer. This scheme is not very efficient and only added for compatibility reasons. + /// + public static class ZebraB64CompressionHelper + { + private static Regex _b64Regex = new Regex(":(B64):(\\S+):([0-9a-fA-F]+)", RegexOptions.Compiled); + + public static string Compress(string hexData) + { + var cleanedHexData = hexData.Replace("\n", string.Empty).Replace("\r", string.Empty); + return Compress(cleanedHexData.ToBytesFromHex()); + } + + public static string Compress(byte[] bytes) + { + var base64 = Convert.ToBase64String(bytes); + var crc16 = Crc16.ComputeHex(base64.EncodeBytes()); + return ":B64:" + base64 + ":" + crc16; + } + + public static byte[] Uncompress(string hexData) + { + var match = _b64Regex.Match(hexData); + if (match.Success) + { + var imageBase64 = match.Groups[2].Value; + return imageBase64.FromBase64(); + } + else + { + throw new FormatException("Hex string not in B64 format"); + } + } + } +} diff --git a/src/BinaryKits.Zpl.Label/Helpers/ZebraZ64CompressionHelper.cs b/src/BinaryKits.Zpl.Label/Helpers/ZebraZ64CompressionHelper.cs new file mode 100644 index 00000000..793dbc2e --- /dev/null +++ b/src/BinaryKits.Zpl.Label/Helpers/ZebraZ64CompressionHelper.cs @@ -0,0 +1,180 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; + +[assembly: InternalsVisibleTo("BinaryKits.Zpl.Label.UnitTest")] + +namespace BinaryKits.Zpl.Label.Helpers +{ + /// + /// Z64 Data Compression Scheme for ~DG and ~DB Commands First compresses the data using the LZ77 algorithm to + /// reduce its size, then compressed data is then encoded using Base64 A CRC is calculated across the Base64-encoded + /// data. If the CRC-check fails or the download is aborted, the object can be invalidated by the printer. reduces + /// the actual number of data bytes and the amount of time required to download graphic images and bitmapped fonts + /// with the ~DG and ~DB commands + /// + public static class ZebraZ64CompressionHelper + { + private static Regex _z64Regex = new Regex(":(Z64):(\\S+):([0-9a-fA-F]+)", RegexOptions.Compiled); + + public static string Compress(string hexData) + { + var cleanedHexData = hexData.Replace("\n", string.Empty).Replace("\r", string.Empty); + return Compress(cleanedHexData.ToBytesFromHex()); + } + + public static string Compress(byte[] bytes) + { +#if NET5_0_OR_GREATER + var data = DeflateCore(bytes); +#else + var data = Deflate(bytes); +#endif + var base64 = data.ToBase64(); + return ":Z64:" + base64 + ":" + Crc16.ComputeHex(base64.EncodeBytes()); + } + + public static byte[] Uncompress(string hexData) + { + var match = _z64Regex.Match(hexData); + if (match.Success) + { + var imageBase64 = match.Groups[2].Value; + var bytes = imageBase64.FromBase64(); +#if NET5_0_OR_GREATER + return InflateCore(bytes); +#else + return Inflate(bytes); +#endif + } + else + { + throw new FormatException("Hex string not in Z64 format"); + } + } + + /// + /// Decompress graphics data with ZLib headers. .NET Standard has no ZlibStream implementation. Need to use + /// DeflateStream and write header and checksum. + /// + /// + /// + internal static byte[] Inflate(byte[] data) + { + using (var outputStream = new MemoryStream()) + { + using (var inputStream = new MemoryStream()) + { + //skip first 2 bytes of headers and last 4 bytes of checksum. + inputStream.Write(data, 2, data.Length - 6); + inputStream.Position = 0; + using (var decompressor = new DeflateStream(inputStream, CompressionMode.Decompress, true)) + { + decompressor.CopyTo(outputStream); + return outputStream.ToArray(); + } + } + } + } + + /// + /// Compress graphics data with ZLib headers .NET Standard has no ZlibStream implementation. + /// Need to use DeflateStream and write header and checksum. + /// Cleaned up implementation based on https://yal.cc/cs-deflatestream-zlib/ + /// + /// + /// + /// + internal static byte[] Deflate(byte[] data, CompressionLevel compressionLevel = (CompressionLevel)3) + { + using (var ms = new MemoryStream()) + { + // write header: + ms.WriteByte(0x78); + // compression level header + ms.WriteByte(GetCompressionHeader(compressionLevel)); + + // write compressed data (with Deflate headers): + var compressor = new DeflateStream(ms, compressionLevel, true); + compressor.Write(data, 0, data.Length); + compressor.Close(); + + // compute Adler-32 checksum + uint a1 = 1, a2 = 0; + foreach (byte b in data) + { + a1 = (a1 + b) % 65521; + a2 = (a2 + a1) % 65521; + } + + //append checksum + ms.WriteByte((byte)(a2 >> 8)); + ms.WriteByte((byte)a2); + ms.WriteByte((byte)(a1 >> 8)); + ms.WriteByte((byte)a1); + ms.Seek(0, SeekOrigin.Begin); + return ms.ToArray(); + } + } + +#if NET5_0_OR_GREATER + /// + /// Compress graphics data with ZLib headers + /// .NET 5.0+ ZlibStream implementation. + /// + /// + /// + /// + internal static byte[] DeflateCore(byte[] data, CompressionLevel compressionLevel = CompressionLevel.SmallestSize) + { + using (var ms = new MemoryStream()) + { + using (var compressor = new ZLibStream(ms, compressionLevel)) + { + compressor.Write(data, 0, data.Length); + } + return ms.ToArray(); + } + } + /// + /// Decompres graphics data with ZLib + /// .NET 5.0+ ZlibStream implementation. + /// + /// + internal static byte[] InflateCore(byte[] data) + { + using (var outputStream = new MemoryStream()) + { + using (var inputStream = new MemoryStream(data)) + { + using (var decompressor = new ZLibStream(inputStream, CompressionMode.Decompress,true)) + { + decompressor.CopyTo(outputStream); + return outputStream.ToArray(); + } + } + } + } +#endif + + private static byte GetCompressionHeader(CompressionLevel level) + { + switch (level) + { + case CompressionLevel.NoCompression: + return 0x01; + case CompressionLevel.Fastest: + return 0x01; + case CompressionLevel.Optimal: + return 0x9C; +#if NET5_0_OR_GREATER + case CompressionLevel.SmallestSize: + return 0xDA; +#endif + } + return 0xDA; + } + } +} diff --git a/src/BinaryKits.Zpl.Label/ImageConverters/ImageResult.cs b/src/BinaryKits.Zpl.Label/ImageConverters/ImageResult.cs index d6d5da70..f6af470e 100644 --- a/src/BinaryKits.Zpl.Label/ImageConverters/ImageResult.cs +++ b/src/BinaryKits.Zpl.Label/ImageConverters/ImageResult.cs @@ -2,7 +2,7 @@ { public class ImageResult { - public string ZplData { get; set; } + public byte[] RawData { get; set; } public int BinaryByteCount { get; set; } public int BytesPerRow { get; set; } } diff --git a/src/BinaryKits.Zpl.Label/ImageConverters/ImageSharpImageConverter.cs b/src/BinaryKits.Zpl.Label/ImageConverters/ImageSharpImageConverter.cs index d96fd088..e1af62a5 100644 --- a/src/BinaryKits.Zpl.Label/ImageConverters/ImageSharpImageConverter.cs +++ b/src/BinaryKits.Zpl.Label/ImageConverters/ImageSharpImageConverter.cs @@ -3,6 +3,7 @@ using System.Collections; using System.IO; using System.Linq; +using System.Runtime.InteropServices.ComTypes; using System.Text; namespace BinaryKits.Zpl.Label.ImageConverters @@ -16,50 +17,52 @@ public class ImageSharpImageConverter : IImageConverter /// public ImageResult ConvertImage(byte[] imageData) { - var zplBuilder = new StringBuilder(); - - using (Image image = Image.Load(imageData).CloneAs()) + using (var ms = new MemoryStream(imageData.Length)) { - var bytesPerRow = image.Width % 8 > 0 - ? image.Width / 8 + 1 - : image.Width / 8; + using (Image image = Image.Load(imageData).CloneAs()) + { + var bytesPerRow = image.Width % 8 > 0 + ? image.Width / 8 + 1 + : image.Width / 8; - var binaryByteCount = image.Height * bytesPerRow; + var binaryByteCount = image.Height * bytesPerRow; - var colorBits = 0; - var j = 0; + var colorBits = 0; + var j = 0; - for (var y = 0; y < image.Height; y++) - { - for (var x = 0; x < image.Width; x++) + for (var y = 0; y < image.Height; y++) { - var pixel = image[x, y]; - - var isBlackPixel = ((pixel.R + pixel.G + pixel.B) / 3) < 128; - if (isBlackPixel) + for (var x = 0; x < image.Width; x++) { - colorBits |= 1 << (7 - j); - } + var pixel = image[x, y]; - j++; + var isBlackPixel = ((pixel.R + pixel.G + pixel.B) / 3) < 128; + if (isBlackPixel) + { + colorBits |= 1 << (7 - j); + } - if (j == 8 || x == (image.Width - 1)) - { - zplBuilder.Append(colorBits.ToString("X2")); - colorBits = 0; - j = 0; + j++; + + if (j == 8 || x == (image.Width - 1)) + { + ms.WriteByte((byte)colorBits); + colorBits = 0; + j = 0; + } } } - zplBuilder.Append('\n'); - } - return new ImageResult - { - ZplData = zplBuilder.ToString(), - BinaryByteCount = binaryByteCount, - BytesPerRow = bytesPerRow - }; + return new ImageResult + { + RawData = ms.ToArray(), + BinaryByteCount = binaryByteCount, + BytesPerRow = bytesPerRow + }; + } } + + } private byte Reverse(byte b) diff --git a/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraHexCompressionHelperTest.cs b/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraACSCompressionHelperTest.cs similarity index 82% rename from src/BinaryKits.Zpl.Protocol.UnitTest/ZebraHexCompressionHelperTest.cs rename to src/BinaryKits.Zpl.Protocol.UnitTest/ZebraACSCompressionHelperTest.cs index fd37e729..2d42e821 100644 --- a/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraHexCompressionHelperTest.cs +++ b/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraACSCompressionHelperTest.cs @@ -4,10 +4,10 @@ namespace BinaryKits.Zpl.Protocol.UnitTest { [TestClass] - public class ZebraHexCompressionHelperTest + public class ZebraACSCompressionHelperTest { [TestMethod] - [DataRow(1, "G")] + //[DataRow(1, "G")] [DataRow(2, "H")] [DataRow(3, "I")] [DataRow(4, "J")] @@ -48,7 +48,7 @@ public class ZebraHexCompressionHelperTest [DataRow(400, "z")] public void GetZebraCharCount_Simple_Successful(int charRepeatCount, string compressed) { - var zebraCharCount = ZebraHexCompressionHelper.GetZebraCharCount(charRepeatCount); + var zebraCharCount = ZebraACSCompressionHelper.GetZebraCharCount(charRepeatCount); Assert.AreEqual(compressed, zebraCharCount); } @@ -70,15 +70,15 @@ public void GetZebraCharCount_Simple_Successful(int charRepeatCount, string comp [DataRow(842, "zzhH")] public void GetZebraCharCount_Complex_Successful(int charRepeatCount, string compressed) { - var zebraCharCount = ZebraHexCompressionHelper.GetZebraCharCount(charRepeatCount); + var zebraCharCount = ZebraACSCompressionHelper.GetZebraCharCount(charRepeatCount); Assert.AreEqual(compressed, zebraCharCount); } [TestMethod] public void Compress_ValidData1_Successful() { - var compressed = ZebraHexCompressionHelper.Compress("FFFF\nF00F\nFFFF\n", 2); - Assert.AreEqual("JFGFH0GFJF", compressed); + var compressed = ZebraACSCompressionHelper.Compress("FFFF\nF00F\nFFFF\n", 2); + Assert.AreEqual("JFFH0FJF", compressed); //^XA //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF //^FO20,20 @@ -89,8 +89,8 @@ public void Compress_ValidData1_Successful() [TestMethod] public void Compress_ValidData2_Successful() { - var compressed = ZebraHexCompressionHelper.Compress("FFFFF00FFFFF", 2); - Assert.AreEqual("JFGFH0GFJF", compressed); + var compressed = ZebraACSCompressionHelper.Compress("FFFFF00FFFFF", 2); + Assert.AreEqual("JFFH0FJF", compressed); //^XA //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF //^FO20,20 @@ -101,8 +101,8 @@ public void Compress_ValidData2_Successful() [TestMethod] public void Compress_ValidData3_Successful() { - var compressed = ZebraHexCompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFFFFFFFFFFFFFFFFFF\n", 10); - Assert.AreEqual("gFG8I0JFJ0JFI0!::JFJ0JFJ0JF::gF", compressed); + var compressed = ZebraACSCompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFFFFFFFFFFFFFFFFFF\n", 10); + Assert.AreEqual("gF8I0JFJ0JFI0!::JFJ0JFJ0JF::gF", compressed); //^XA //~DGR:SAMPLE.GRF,00080,010,gFG8I0JFJ0JFI0!::JFJ0JFJ0JF::gF //^FO20,20 @@ -115,8 +115,8 @@ public void CompressUncompress_Flow_Successful() { var originalData = "FFFFFFFFFFFFFFFFFFFF\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\n8000FFFF0000FFFF0001\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFF0000FFFF0000FFFF\nFFFFFFFFFFFFFFFFFFFF\n"; - var compressed = ZebraHexCompressionHelper.Compress(originalData, 10); - var uncompressed = ZebraHexCompressionHelper.Uncompress(compressed, 10); + var compressed = ZebraACSCompressionHelper.Compress(originalData, 10); + var uncompressed = ZebraACSCompressionHelper.Uncompress(compressed, 10); Assert.AreEqual(originalData, uncompressed); } @@ -125,7 +125,7 @@ public void Uncompress_ValidData1_Successful() { var compressedData = "gO0E\n"; - var uncompressed = ZebraHexCompressionHelper.Uncompress(compressedData, 10); + var uncompressed = ZebraACSCompressionHelper.Uncompress(compressedData, 10); Assert.AreEqual("00000000000000000000000000000E\n", uncompressed); } @@ -134,7 +134,7 @@ public void Uncompress_ValidData2_Successful() { var compressedData = "gO0GE\n"; - var uncompressed = ZebraHexCompressionHelper.Uncompress(compressedData, 10); + var uncompressed = ZebraACSCompressionHelper.Uncompress(compressedData, 10); Assert.AreEqual("00000000000000000000000000000E\n", uncompressed); } } diff --git a/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraB64CompressionHelperTest.cs b/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraB64CompressionHelperTest.cs new file mode 100644 index 00000000..b2f12509 --- /dev/null +++ b/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraB64CompressionHelperTest.cs @@ -0,0 +1,31 @@ +using BinaryKits.Zpl.Protocol.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; + +namespace BinaryKits.Zpl.Protocol.UnitTest +{ + [TestClass] + public class ZebraB64CompressionHelperTest + { + [TestMethod] + public void Compress_ValidData1_Successful() + { + var compressed = ZebraB64CompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"); + Assert.AreEqual(":B64://///////////4AA//8AAP//AAGAAP//AAD//wABgAD//wAA//8AAf//AAD//wAA/////wAA//8AAP////8AAP//AAD///////////////8=:e4b3", compressed); + //^XA + //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF + //^FO20,20 + //^XGR:SAMPLE.GRF,1,1^FS + //^XZ + } + [TestMethod] + public void CompressUncompress_Flow_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + string compressed = ZebraB64CompressionHelper.Compress(originalData); + string uncompressed = ZebraB64CompressionHelper.Uncompress(compressed).ToHexFromBytes(); + Assert.AreEqual(originalData, uncompressed); + } + } +} diff --git a/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraZ64CompressionHelperTest.cs b/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraZ64CompressionHelperTest.cs new file mode 100644 index 00000000..08181aef --- /dev/null +++ b/src/BinaryKits.Zpl.Protocol.UnitTest/ZebraZ64CompressionHelperTest.cs @@ -0,0 +1,88 @@ +using BinaryKits.Zpl.Protocol.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.IO.Compression; + +namespace BinaryKits.Zpl.Protocol.UnitTest +{ + [TestClass] + public class ZebraZ64CompressionHelperTest + { + [TestMethod] + public void Compress_ValidData1_Successful() + { + var compressed = ZebraZ64CompressionHelper.Compress("FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"); + Assert.AreEqual(":Z64:eNr7/x8GGhj+/2cAYUZsLCjNAFKJjQUDAOLDM1I=:8f39", compressed); + //^XA + //~DGR:SAMPLE.GRF,00006,02,JFGFH0GFJF + //^FO20,20 + //^XGR:SAMPLE.GRF,1,1^FS + //^XZ + } + [TestMethod] + public void CompressUncompress_Flow_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + string compressed = ZebraZ64CompressionHelper.Compress(originalData); + string uncompressed = ZebraZ64CompressionHelper.Uncompress(compressed).ToHexFromBytes(); + Assert.AreEqual(originalData, uncompressed); + } +#if NET5_0_OR_GREATER + [TestMethod] + public void DeflateNetStandardSameAsNetCore_Optimal_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.Optimal; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void DeflateNetStandardSameAsNetCore_Fastest_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.Fastest; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void DeflateNetStandardSameAsNetCore_SmallestSize_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.SmallestSize; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void DeflateNetStandardSameAsNetCore_NoCompression_Successful() + { + var originalData = "FFFFFFFFFFFFFFFFFFFF8000FFFF0000FFFF00018000FFFF0000FFFF00018000FFFF0000FFFF0001FFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFF0000FFFF0000FFFFFFFFFFFFFFFFFFFFFFFF"; + + var compressionLevel = CompressionLevel.NoCompression; + string compressed = ZebraZ64CompressionHelper.Deflate(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.DeflateCore(originalData.ToBytesFromHex(), compressionLevel).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } + [TestMethod] + public void InflateNetStandardSameAsNetCore_Successful() + { + var originalData = "eJz7/x8GGhj+/2cAYUZsLCgNxNhZMAAA4sMzUg=="; + + string compressed = ZebraZ64CompressionHelper.Inflate(originalData.FromBase64()).ToHexFromBytes(); + string compressedCore = ZebraZ64CompressionHelper.InflateCore(originalData.FromBase64()).ToHexFromBytes(); + + Assert.AreEqual(compressed, compressedCore); + } +#endif + } +} diff --git a/src/BinaryKits.Zpl.Protocol/Helpers/ByteHelper.cs b/src/BinaryKits.Zpl.Protocol/Helpers/ByteHelper.cs new file mode 100644 index 00000000..f19a0e8d --- /dev/null +++ b/src/BinaryKits.Zpl.Protocol/Helpers/ByteHelper.cs @@ -0,0 +1,76 @@ +using System; +using System.Linq; +using System.Text; + +namespace BinaryKits.Zpl.Protocol.Helpers +{ + public static class ByteHelper + { + public static string ToBase64(this byte[] bytes) { return Convert.ToBase64String(bytes); } + + public static byte[] FromBase64(this string base64) { return Convert.FromBase64String(base64); } + + public static string ToHexFromBytes(this byte[] bytes) { return BytesToHex(bytes); } + + public static byte[] ToBytesFromHex(this string hex) { return HexToBytes(hex); } + + public static byte[] EncodeBytes(this string hex) { return Encoding.ASCII.GetBytes(hex); } + + public static byte[] HexToBytes(string hex) + { + if (hex.IndexOfAny(new[] { '\r', '\n' }) != -1) + { + hex = hex.Replace("\n", string.Empty); + hex = hex.Replace("\r", string.Empty); + } + //Not Available on .NET standard and has much greater performance due to Vectorization. +#if NET5_0_OR_GREATER + return Convert.FromHexString(hex); +#else + if (hex.Length % 2 == 1) + { + throw new Exception("The binary key cannot have an odd number of digits"); + } + + var array = new byte[hex.Length >> 1]; + for (var i = 0; i < hex.Length >> 1; ++i) + { + array[i] = (byte)((GetHexVal(hex[i << 1]) << 4) + GetHexVal(hex[(i << 1) + 1])); + } + + return array; +#endif + } + + public static string BytesToHex(byte[] bytes) + { + //Not Available on .NET standard and has much greater performance due to Vectorization. +#if NET5_0_OR_GREATER + return Convert.ToHexString(bytes); +#else + var c = new char[bytes.Length * 2]; + int b; + for (var i = 0; i < bytes.Length; i++) + { + b = bytes[i] >> 4; + c[i * 2] = (char)(55 + b + (b - 10 >> 31 & -7)); + b = bytes[i] & 0xF; + c[i * 2 + 1] = (char)(55 + b + (b - 10 >> 31 & -7)); + } + + return new string(c); +#endif + } + + private static int GetHexVal(char hex) + { + int val = hex; + //For uppercase A-F letters: + //return val - (val < 58 ? 48 : 55); + //For lowercase a-f letters: + //return val - (val < 58 ? 48 : 87); + //Or the two combined, but a bit slower: + return val - (val < 58 ? 48 : val < 97 ? 55 : 87); + } + } +} diff --git a/src/BinaryKits.Zpl.Protocol/Helpers/Crc16.cs b/src/BinaryKits.Zpl.Protocol/Helpers/Crc16.cs new file mode 100644 index 00000000..3c65bddb --- /dev/null +++ b/src/BinaryKits.Zpl.Protocol/Helpers/Crc16.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; + +namespace BinaryKits.Zpl.Protocol.Helpers +{ + /// + /// Calculate Crc16 Checksum. + /// + internal static class Crc16 + { + private static ushort[] crc16_table = { + 0x0000, 0x1021, 0x2042, 0x3063, + 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, + 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF + }; + + public static ushort Compute(byte[] bytes) + { + int crc = 0; + foreach (byte b in bytes) + { + crc ^= (b << 8); + crc = (crc << 4) ^ crc16_table[(crc >> 12) & 0xF]; + crc = (crc << 4) ^ crc16_table[(crc >> 12) & 0xF]; + } + + return (ushort)(crc & 0xFFFF); + } + + public static string ComputeHex(byte[] bytes) + { + return Compute(bytes).ToString("x4"); + } + + } +} diff --git a/src/BinaryKits.Zpl.Protocol/Helpers/ZebraHexCompressionHelper.cs b/src/BinaryKits.Zpl.Protocol/Helpers/ZebraACSCompressionHelper.cs similarity index 94% rename from src/BinaryKits.Zpl.Protocol/Helpers/ZebraHexCompressionHelper.cs rename to src/BinaryKits.Zpl.Protocol/Helpers/ZebraACSCompressionHelper.cs index 96fe4ee8..c192d27e 100644 --- a/src/BinaryKits.Zpl.Protocol/Helpers/ZebraHexCompressionHelper.cs +++ b/src/BinaryKits.Zpl.Protocol/Helpers/ZebraACSCompressionHelper.cs @@ -6,12 +6,12 @@ namespace BinaryKits.Zpl.Protocol.Helpers { /// - /// Alternative Data Compression Scheme for ~DG and ~DB Commands + /// Alternative Data Compression Scheme (ACS) for ~DG and ~DB Commands /// There is an alternative data compression scheme recognized by the Zebra printer. This scheme further /// reduces the actual number of data bytes and the amount of time required to download graphic images and /// bitmapped fonts with the ~DG and ~DB commands /// - public static class ZebraHexCompressionHelper + public static class ZebraACSCompressionHelper { /// /// MinCompressionBlockCount (CompressionCountMapping -> g) @@ -94,7 +94,7 @@ public static string Compress(string hexData, int bytesPerRow) compressedCurrentLine.Append(lastChar); } - if (compressedCurrentLine.Equals(compressedPreviousLine)) + if (compressedCurrentLine.ToString().Equals(compressedPreviousLine)) { //previous line is repeated compressedLines.Append(':'); @@ -179,13 +179,13 @@ public static string Uncompress(string compressedHexData, int bytesPerRow) return hexData.ToString(); } - /// - /// Get Zebra char repeat count - /// - /// - /// public static string GetZebraCharCount(int charRepeatCount) { + //only append if the repeated character is than 1 otherwise the compression scheme uses 2 characters to represent 1 + if (charRepeatCount == 1) + { + return string.Empty; + } if (CompressionCountMapping.TryGetValue(charRepeatCount, out var compressionKey)) { return $"{compressionKey}"; diff --git a/src/BinaryKits.Zpl.Protocol/Helpers/ZebraB64CompressionHelper.cs b/src/BinaryKits.Zpl.Protocol/Helpers/ZebraB64CompressionHelper.cs new file mode 100644 index 00000000..facf0750 --- /dev/null +++ b/src/BinaryKits.Zpl.Protocol/Helpers/ZebraB64CompressionHelper.cs @@ -0,0 +1,42 @@ +using System; +using System.Text.RegularExpressions; + +namespace BinaryKits.Zpl.Protocol.Helpers +{ + /// + /// B64 Data Compression Scheme for ~DG and ~DB Commands Raw data is encoded using Base64 A CRC is calculated across + /// the Base64-encoded data. If the CRC-check fails or the download is aborted, the object can be invalidated by the + /// printer. This scheme is not very efficient and only added for compatibility reasons. + /// + public static class ZebraB64CompressionHelper + { + private static Regex _b64Regex = new Regex(":(B64):(\\S+):([0-9a-fA-F]+)", RegexOptions.Compiled); + + public static string Compress(string hexData) + { + var cleanedHexData = hexData.Replace("\n", string.Empty).Replace("\r", string.Empty); + return Compress(cleanedHexData.ToBytesFromHex()); + } + + public static string Compress(byte[] bytes) + { + var base64 = Convert.ToBase64String(bytes); + var crc16 = Crc16.ComputeHex(base64.EncodeBytes()); + return ":B64:" + base64 + ":" + crc16; + } + + public static byte[] Uncompress(string hexData) + { + var match = _b64Regex.Match(hexData); + if (match.Success) + { + var imageBase64 = match.Groups[2].Value; + return imageBase64.FromBase64(); + } + else + { + throw new FormatException("Hex string not in B64 format"); + } + } + } +} diff --git a/src/BinaryKits.Zpl.Protocol/Helpers/ZebraZ64CompressionHelper.cs b/src/BinaryKits.Zpl.Protocol/Helpers/ZebraZ64CompressionHelper.cs new file mode 100644 index 00000000..a1201f26 --- /dev/null +++ b/src/BinaryKits.Zpl.Protocol/Helpers/ZebraZ64CompressionHelper.cs @@ -0,0 +1,180 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; + +[assembly: InternalsVisibleTo("BinaryKits.Zpl.Protocol.UnitTest")] + +namespace BinaryKits.Zpl.Protocol.Helpers +{ + /// + /// Z64 Data Compression Scheme for ~DG and ~DB Commands First compresses the data using the LZ77 algorithm to + /// reduce its size, then compressed data is then encoded using Base64 A CRC is calculated across the Base64-encoded + /// data. If the CRC-check fails or the download is aborted, the object can be invalidated by the printer. reduces + /// the actual number of data bytes and the amount of time required to download graphic images and bitmapped fonts + /// with the ~DG and ~DB commands + /// + public static class ZebraZ64CompressionHelper + { + private static Regex _z64Regex = new Regex(":(Z64):(\\S+):([0-9a-fA-F]+)", RegexOptions.Compiled); + + public static string Compress(string hexData) + { + var cleanedHexData = hexData.Replace("\n", string.Empty).Replace("\r", string.Empty); + return Compress(cleanedHexData.ToBytesFromHex()); + } + + public static string Compress(byte[] bytes) + { +#if NET5_0_OR_GREATER + var data = DeflateCore(bytes); +#else + var data = Deflate(bytes); +#endif + var base64 = data.ToBase64(); + return ":Z64:" + base64 + ":" + Crc16.ComputeHex(base64.EncodeBytes()); + } + + public static byte[] Uncompress(string hexData) + { + var match = _z64Regex.Match(hexData); + if (match.Success) + { + var imageBase64 = match.Groups[2].Value; + var bytes = imageBase64.FromBase64(); +#if NET5_0_OR_GREATER + return InflateCore(bytes); +#else + return Inflate(bytes); +#endif + } + else + { + throw new FormatException("Hex string not in Z64 format"); + } + } + + /// + /// Decompress graphics data with ZLib headers. .NET Standard has no ZlibStream implementation. Need to use + /// DeflateStream and write header and checksum. + /// + /// + /// + internal static byte[] Inflate(byte[] data) + { + using (var outputStream = new MemoryStream()) + { + using (var inputStream = new MemoryStream()) + { + //skip first 2 bytes of headers and last 4 bytes of checksum. + inputStream.Write(data, 2, data.Length - 6); + inputStream.Position = 0; + using (var decompressor = new DeflateStream(inputStream, CompressionMode.Decompress, true)) + { + decompressor.CopyTo(outputStream); + return outputStream.ToArray(); + } + } + } + } + + /// + /// Compress graphics data with ZLib headers .NET Standard has no ZlibStream implementation. + /// Need to use DeflateStream and write header and checksum. + /// Cleaned up implementation based on https://yal.cc/cs-deflatestream-zlib/ + /// + /// + /// + /// + internal static byte[] Deflate(byte[] data, CompressionLevel compressionLevel = (CompressionLevel)3) + { + using (var ms = new MemoryStream()) + { + // write header: + ms.WriteByte(0x78); + // compression level header + ms.WriteByte(GetCompressionHeader(compressionLevel)); + + // write compressed data (with Deflate headers): + var compressor = new DeflateStream(ms, compressionLevel, true); + compressor.Write(data, 0, data.Length); + compressor.Close(); + + // compute Adler-32 checksum + uint a1 = 1, a2 = 0; + foreach (byte b in data) + { + a1 = (a1 + b) % 65521; + a2 = (a2 + a1) % 65521; + } + + //append checksum + ms.WriteByte((byte)(a2 >> 8)); + ms.WriteByte((byte)a2); + ms.WriteByte((byte)(a1 >> 8)); + ms.WriteByte((byte)a1); + ms.Seek(0, SeekOrigin.Begin); + return ms.ToArray(); + } + } + +#if NET5_0_OR_GREATER + /// + /// Compress graphics data with ZLib headers + /// .NET 5.0+ ZlibStream implementation. + /// + /// + /// + /// + internal static byte[] DeflateCore(byte[] data, CompressionLevel compressionLevel = CompressionLevel.SmallestSize) + { + using (var ms = new MemoryStream()) + { + using (var compressor = new ZLibStream(ms, compressionLevel)) + { + compressor.Write(data, 0, data.Length); + } + return ms.ToArray(); + } + } + /// + /// Decompres graphics data with ZLib + /// .NET 5.0+ ZlibStream implementation. + /// + /// + internal static byte[] InflateCore(byte[] data) + { + using (var outputStream = new MemoryStream()) + { + using (var inputStream = new MemoryStream(data)) + { + using (var decompressor = new ZLibStream(inputStream, CompressionMode.Decompress,true)) + { + decompressor.CopyTo(outputStream); + return outputStream.ToArray(); + } + } + } + } +#endif + + private static byte GetCompressionHeader(CompressionLevel level) + { + switch (level) + { + case CompressionLevel.NoCompression: + return 0x01; + case CompressionLevel.Fastest: + return 0x01; + case CompressionLevel.Optimal: + return 0x9C; +#if NET5_0_OR_GREATER + case CompressionLevel.SmallestSize: + return 0xDA; +#endif + } + return 0xDA; + } + } +} diff --git a/src/BinaryKits.Zpl.TestConsole/Program.cs b/src/BinaryKits.Zpl.TestConsole/Program.cs index aed0dae3..7c68faa2 100644 --- a/src/BinaryKits.Zpl.TestConsole/Program.cs +++ b/src/BinaryKits.Zpl.TestConsole/Program.cs @@ -17,16 +17,19 @@ static async Task Main(string[] args) RenderLabel1, RenderLabel2, RenderLabel3, - RenderLabel4, + RenderLabel4_ACS, + RenderLabel4_Z64, + RenderLabel4_B64, RenderLabel5, RenderLabel6, }; foreach (var renderAction in renderActions) { - Console.WriteLine(renderAction.Method.Name); + var methodName = renderAction.Method.Name; + Console.WriteLine(methodName); var zplData = renderAction.Invoke(); - await RenderPreviewAsync(zplData); + await RenderPreviewAsync(zplData, methodName); } } @@ -96,7 +99,7 @@ static string RenderLabel3() }); } - static string RenderLabel4() + static string RenderLabel4_ACS() { var elements = new ZplElementBase[] { @@ -112,6 +115,38 @@ static string RenderLabel4() TargetPrintDpi = 203 }); } + static string RenderLabel4_Z64() + { + var elements = new ZplElementBase[] + { + new ZplDownloadGraphics('R', "TEST", File.ReadAllBytes("logo_sw.png"),ZplCompressionScheme.Z64), + new ZplRecallGraphic(0, 0, 'R', "TEST") + }; + + var renderEngine = new ZplEngine(elements); + return renderEngine.ToZplString(new ZplRenderOptions + { + //AddEmptyLineBeforeElementStart = true, + SourcePrintDpi = 203, + TargetPrintDpi = 203 + }); + } + static string RenderLabel4_B64() + { + var elements = new ZplElementBase[] + { + new ZplDownloadGraphics('R', "TEST", File.ReadAllBytes("logo_sw.png"),ZplCompressionScheme.B64), + new ZplRecallGraphic(0, 0, 'R', "TEST") + }; + + var renderEngine = new ZplEngine(elements); + return renderEngine.ToZplString(new ZplRenderOptions + { + //AddEmptyLineBeforeElementStart = true, + SourcePrintDpi = 203, + TargetPrintDpi = 203 + }); + } static string RenderLabel5() { @@ -170,8 +205,20 @@ static string RenderLabel6() }); } - static async Task RenderPreviewAsync(string zplData) + + static async Task RenderPreviewAsync(string zplData, string methodName) { + var zplFilename = $"{methodName}-{Guid.NewGuid()}.txt"; + await File.WriteAllTextAsync(zplFilename, zplData); + var zplStartInfo = new ProcessStartInfo + { + FileName = zplFilename, + UseShellExecute = true, + CreateNoWindow = true, + Verb = string.Empty + }; + + Process.Start(zplStartInfo); var client = new LabelaryClient(); var previewData = await client.GetPreviewAsync(zplData, PrintDensity.PD8dpmm, new LabelSize(6, 8, Measure.Inch)); if (previewData.Length == 0) @@ -179,7 +226,7 @@ static async Task RenderPreviewAsync(string zplData) return; } - var fileName = $"preview-{Guid.NewGuid()}.png"; + var fileName = $"{methodName}-{Guid.NewGuid()}.png"; await File.WriteAllBytesAsync(fileName, previewData); var processStartInfo = new ProcessStartInfo diff --git a/src/BinaryKits.Zpl.TestConsole/Properties/launchSettings.json b/src/BinaryKits.Zpl.TestConsole/Properties/launchSettings.json new file mode 100644 index 00000000..ca82a120 --- /dev/null +++ b/src/BinaryKits.Zpl.TestConsole/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "BinaryKits.Zpl.TestConsole": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL2", + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadGraphicsZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadGraphicsZplCommandAnalyzer.cs index f5a4cc74..db268a3d 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadGraphicsZplCommandAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadGraphicsZplCommandAnalyzer.cs @@ -1,24 +1,23 @@ using BinaryKits.Zpl.Label.Elements; -using BinaryKits.Zpl.Label.Helpers; using BinaryKits.Zpl.Label.ImageConverters; using BinaryKits.Zpl.Viewer.Helpers; +using System; +using System.Data; using System.Text.RegularExpressions; namespace BinaryKits.Zpl.Viewer.CommandAnalyzers { public class DownloadGraphicsZplCommandAnalyzer : ZplCommandAnalyzerBase { - private static readonly Regex commandRegex = new Regex(@"^~DG(\w:)?(.*?\..+?),(\d+),(\d+),(.+)$", RegexOptions.Compiled); - private static readonly Regex hexDataRegex = new Regex("^[0-9A-Fa-f]+$", RegexOptions.Compiled); - + private static readonly Regex commandRegex = new Regex( @"^~DG(\w:)?(.*?\..+?),(\d+),(\d+),(.+)$",RegexOptions.Compiled); private readonly IPrinterStorage _printerStorage; public DownloadGraphicsZplCommandAnalyzer( - VirtualPrinter virtualPrinter, - IPrinterStorage printerStorage) - : base("~DG", virtualPrinter) - { - this._printerStorage = printerStorage; + VirtualPrinter virtualPrinter, + IPrinterStorage printerStorage) + : base("~DG",virtualPrinter) + { + this._printerStorage = printerStorage; } public override ZplElementBase Analyze(string zplCommand) @@ -32,12 +31,7 @@ public override ZplElementBase Analyze(string zplCommand) _ = int.TryParse(commandMatch.Groups[4].Value, out var numberOfBytesPerRow); var dataHex = commandMatch.Groups[5].Value; - - if (!hexDataRegex.IsMatch(dataHex)) - { - dataHex = ZebraHexCompressionHelper.Uncompress(dataHex, numberOfBytesPerRow); - } - var grfImageData = ByteHelper.HexToBytes(dataHex); + byte[] grfImageData = ImageHelper.GetImageBytes(dataHex, numberOfBytesPerRow); // assert grfImageData.Length == totalNumberOfBytesInGraphic diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadObjectsZplCommandAnaylzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadObjectsZplCommandAnaylzer.cs index 3ffaa1dc..34590a94 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadObjectsZplCommandAnaylzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/DownloadObjectsZplCommandAnaylzer.cs @@ -1,4 +1,5 @@ using BinaryKits.Zpl.Label.Elements; +using BinaryKits.Zpl.Label.Helpers; using BinaryKits.Zpl.Viewer.Helpers; namespace BinaryKits.Zpl.Viewer.CommandAnalyzers @@ -27,6 +28,7 @@ public override ZplElementBase Analyze(string zplCommand) _ = int.TryParse(zplDataParts[3], out var objectDataLength); _ = int.TryParse(zplDataParts[4], out var totalNumberOfBytesPerRow); + //TODO: Handle case when .GRF data is downloaded using the ~DY command var dataHex = zplDataParts[5]; this._printerStorage.AddFile(storageDevice, objectName, ByteHelper.HexToBytes(dataHex)); diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicFieldZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicFieldZplCommandAnalyzer.cs index e9fd938d..3ce90178 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicFieldZplCommandAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicFieldZplCommandAnalyzer.cs @@ -48,12 +48,8 @@ public override ZplElementBase Analyze(string zplCommand) var indexOfFourthComma = this.IndexOfNthCharacter(zplCommand, 4, ','); var dataHex = zplCommand.Substring(indexOfFourthComma + 1); - if (dataHex.Length != binaryByteCount * 2) - { - dataHex = ZebraHexCompressionHelper.Uncompress(dataHex, bytesPerRow); - } + byte[] grfImageData = ImageHelper.GetImageBytes(dataHex, bytesPerRow); - var grfImageData = ByteHelper.HexToBytes(dataHex); var converter = new ImageSharpImageConverter(); var imageData = converter.ConvertImage(grfImageData, bytesPerRow); dataHex = ByteHelper.BytesToHex(imageData); diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs index 41b208cd..350ddfd6 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs @@ -1,4 +1,5 @@ using BinaryKits.Zpl.Label.Elements; +using BinaryKits.Zpl.Label.Helpers; using BinaryKits.Zpl.Viewer.Helpers; using SkiaSharp; diff --git a/src/BinaryKits.Zpl.Viewer/Helpers/ImageHelper.cs b/src/BinaryKits.Zpl.Viewer/Helpers/ImageHelper.cs new file mode 100644 index 00000000..f5df3054 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/Helpers/ImageHelper.cs @@ -0,0 +1,30 @@ +using BinaryKits.Zpl.Label.Helpers; +using System; +using System.Text.RegularExpressions; + +namespace BinaryKits.Zpl.Viewer.Helpers +{ + internal static class ImageHelper + { + private static readonly Regex hexDataRegex = new Regex("^[0-9A-Fa-f]+$", RegexOptions.Compiled); + private static readonly Regex z64DataRegex = new Regex(":(Z64):(\\S+):([0-9a-fA-F]+)", RegexOptions.Compiled); + private static readonly Regex b64DataRegex = new Regex(":(B64):(\\S+):([0-9a-fA-F]+)", RegexOptions.Compiled); + + public static byte[] GetImageBytes(string dataHex, int bytesPerRow) + { + if (z64DataRegex.IsMatch(dataHex)) + { + return ZebraZ64CompressionHelper.Uncompress(dataHex); + } + if (b64DataRegex.IsMatch(dataHex)) + { + return ZebraB64CompressionHelper.Uncompress(dataHex); + } + if (hexDataRegex.IsMatch(dataHex)) + { + return dataHex.ToBytesFromHex(); + } + return ZebraACSCompressionHelper.Uncompress(dataHex, bytesPerRow).ToBytesFromHex(); + } + } +}