C# Zebra 十六进制压缩算法
C# Zebra hex compress algorithm
我目前正在从事与 Zebra 打印机通信的项目。我已经有办法
compress the hex data (The Method EncodeHexAscii compress the data). But I can't find a way to decompress the data. It is a special compress logic for hex data. In the documentation of the zebra printer is only this info available Alternative Data Compression Scheme for ~DG and ~DB Commands
我现在已经为问题开发了自己的逻辑,源代码也可以在 GitHub repository 中找到。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ZebraHelper
{
/// <summary>
/// Alternative Data Compression Scheme 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
/// </summary>
public static class ZebraHexCompressionHelper
{
/// <summary>
/// MinCompressionBlockCount (CompressionCountMapping -> g)
/// </summary>
const int MinCompressionBlockCount = 20;
/// <summary>
/// CompressionCountMapping (CompressionCountMapping -> z)
/// </summary>
const int MaxCompressionRepeatCount = 400;
/// <summary>
/// The mapping table used for compression.
/// Each character count (the key) is represented by a certain char (the value).
/// </summary>
private static readonly Dictionary<int, char> CompressionCountMapping = new Dictionary<int, char>()
{
{1, 'G'}, {2, 'H'}, {3, 'I'}, {4, 'J'}, {5, 'K'}, {6, 'L'}, {7, 'M'}, {8, 'N'}, {9, 'O' }, {10, 'P'},
{11, 'Q'}, {12, 'R'}, {13, 'S'}, {14, 'T'}, {15, 'U'}, {16, 'V'}, {17, 'W'}, {18, 'X'}, {19, 'Y'},
{20, 'g'}, {40, 'h'}, {60, 'i'}, {80, 'j' }, {100, 'k'}, {120, 'l'}, {140, 'm'}, {160, 'n'}, {180, 'o'}, {200, 'p'},
{220, 'q'}, {240, 'r'}, {260, 's'}, {280, 't'}, {300, 'u'}, {320, 'v'}, {340, 'w'}, {360, 'x'}, {380, 'y'}, {400, 'z' }
};
private static IEnumerable<string> Split(string str, int chunkSize)
{
return Enumerable.Range(0, str.Length / chunkSize).Select(i => str.Substring(i * chunkSize, chunkSize));
}
/// <summary>
/// Compress hex
/// </summary>
/// <param name="hexData">Clean hex data 000000\nFFFFFF</param>
/// <param name="bytesPerRow"></param>
/// <returns></returns>
public static string Compress(string hexData, int bytesPerRow)
{
var chunkSize = bytesPerRow * 2;
var cleanedHexData = hexData.Replace("\n", string.Empty).Replace("\r", string.Empty);
var compressedLines = new StringBuilder(hexData.Length);
var compressedCurrentLine = new StringBuilder(chunkSize);
var compressedPreviousLine = string.Empty;
var dataLines = Split(cleanedHexData, chunkSize);
foreach (var dataLine in dataLines)
{
var lastChar = dataLine[0];
var charRepeatCount = 1;
for (var i = 1; i < dataLine.Length; i++)
{
if (dataLine[i] == lastChar)
{
charRepeatCount++;
continue;
}
//char changed within the line
compressedCurrentLine.Append(GetZebraCharCount(charRepeatCount));
compressedCurrentLine.Append(lastChar);
lastChar = dataLine[i];
charRepeatCount = 1;
}
//process last char in dataLine
if (lastChar.Equals('0'))
{
//fills the line, to the right, with zeros
compressedCurrentLine.Append(',');
}
else if (lastChar.Equals('1'))
{
//fills the line, to the right, with ones
compressedCurrentLine.Append('!');
}
else
{
compressedCurrentLine.Append(GetZebraCharCount(charRepeatCount));
compressedCurrentLine.Append(lastChar);
}
if (compressedCurrentLine.Equals(compressedPreviousLine))
{
//previous line is repeated
compressedLines.Append(':');
}
else
{
compressedLines.Append(compressedCurrentLine);
}
compressedPreviousLine = compressedCurrentLine.ToString();
compressedCurrentLine.Clear();
}
return compressedLines.ToString();
}
/// <summary>
/// Uncompress data
/// </summary>
/// <param name="compressedHexData"></param>
/// <param name="bytesPerRow"></param>
/// <returns></returns>
public static string Uncompress(string compressedHexData, int bytesPerRow)
{
var hexData = new StringBuilder();
var chunkSize = bytesPerRow * 2;
var lineIndex = 0;
var totalCount = 0;
var reverseMapping = CompressionCountMapping.ToDictionary(o => o.Value, o => o.Key);
foreach (var c in compressedHexData)
{
if (c.Equals(':'))
{
var appendRepeat = hexData.ToString().Substring(hexData.Length - (chunkSize + 1));
hexData.Append(appendRepeat);
continue;
}
if (c.Equals(','))
{
var appendZero = new string('0', chunkSize - lineIndex);
hexData.Append(appendZero);
hexData.Append("\n");
lineIndex = 0;
continue;
}
if (c.Equals('!'))
{
var appendOne = new string('1', chunkSize - lineIndex);
hexData.Append(appendOne);
hexData.Append("\n");
lineIndex = 0;
continue;
}
if (reverseMapping.TryGetValue(c, out var count))
{
totalCount += count;
continue;
}
var append = new string(c, totalCount);
hexData.Append(append);
lineIndex += append.Length;
totalCount = 0;
if (lineIndex == chunkSize)
{
hexData.Append("\n");
lineIndex = 0;
}
}
return hexData.ToString();
}
public static string GetZebraCharCount(int charRepeatCount)
{
if (CompressionCountMapping.TryGetValue(charRepeatCount, out var compressionKey))
{
return $"{compressionKey}";
}
var compressionKeys = new StringBuilder(5);
var multi20 = charRepeatCount / MinCompressionBlockCount * MinCompressionBlockCount;
var remainder = charRepeatCount % multi20;
while (remainder > 0 || multi20 > 0)
{
if (multi20 > MaxCompressionRepeatCount)
{
remainder += multi20 - MaxCompressionRepeatCount;
multi20 = MaxCompressionRepeatCount;
}
if (!CompressionCountMapping.TryGetValue(multi20, out compressionKey))
{
throw new Exception("Compression failure");
}
compressionKeys.Append(compressionKey);
if (remainder == 0)
{
break;
}
if (remainder <= 20)
{
CompressionCountMapping.TryGetValue(remainder, out compressionKey);
compressionKeys.Append(compressionKey);
break;
}
multi20 = remainder / MinCompressionBlockCount * MinCompressionBlockCount;
remainder %= multi20;
}
return compressionKeys.ToString();
}
}
}
我目前正在从事与 Zebra 打印机通信的项目。我已经有办法
compress the hex data (The Method EncodeHexAscii compress the data). But I can't find a way to decompress the data. It is a special compress logic for hex data. In the documentation of the zebra printer is only this info available Alternative Data Compression Scheme for ~DG and ~DB Commands
我现在已经为问题开发了自己的逻辑,源代码也可以在 GitHub repository 中找到。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ZebraHelper
{
/// <summary>
/// Alternative Data Compression Scheme 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
/// </summary>
public static class ZebraHexCompressionHelper
{
/// <summary>
/// MinCompressionBlockCount (CompressionCountMapping -> g)
/// </summary>
const int MinCompressionBlockCount = 20;
/// <summary>
/// CompressionCountMapping (CompressionCountMapping -> z)
/// </summary>
const int MaxCompressionRepeatCount = 400;
/// <summary>
/// The mapping table used for compression.
/// Each character count (the key) is represented by a certain char (the value).
/// </summary>
private static readonly Dictionary<int, char> CompressionCountMapping = new Dictionary<int, char>()
{
{1, 'G'}, {2, 'H'}, {3, 'I'}, {4, 'J'}, {5, 'K'}, {6, 'L'}, {7, 'M'}, {8, 'N'}, {9, 'O' }, {10, 'P'},
{11, 'Q'}, {12, 'R'}, {13, 'S'}, {14, 'T'}, {15, 'U'}, {16, 'V'}, {17, 'W'}, {18, 'X'}, {19, 'Y'},
{20, 'g'}, {40, 'h'}, {60, 'i'}, {80, 'j' }, {100, 'k'}, {120, 'l'}, {140, 'm'}, {160, 'n'}, {180, 'o'}, {200, 'p'},
{220, 'q'}, {240, 'r'}, {260, 's'}, {280, 't'}, {300, 'u'}, {320, 'v'}, {340, 'w'}, {360, 'x'}, {380, 'y'}, {400, 'z' }
};
private static IEnumerable<string> Split(string str, int chunkSize)
{
return Enumerable.Range(0, str.Length / chunkSize).Select(i => str.Substring(i * chunkSize, chunkSize));
}
/// <summary>
/// Compress hex
/// </summary>
/// <param name="hexData">Clean hex data 000000\nFFFFFF</param>
/// <param name="bytesPerRow"></param>
/// <returns></returns>
public static string Compress(string hexData, int bytesPerRow)
{
var chunkSize = bytesPerRow * 2;
var cleanedHexData = hexData.Replace("\n", string.Empty).Replace("\r", string.Empty);
var compressedLines = new StringBuilder(hexData.Length);
var compressedCurrentLine = new StringBuilder(chunkSize);
var compressedPreviousLine = string.Empty;
var dataLines = Split(cleanedHexData, chunkSize);
foreach (var dataLine in dataLines)
{
var lastChar = dataLine[0];
var charRepeatCount = 1;
for (var i = 1; i < dataLine.Length; i++)
{
if (dataLine[i] == lastChar)
{
charRepeatCount++;
continue;
}
//char changed within the line
compressedCurrentLine.Append(GetZebraCharCount(charRepeatCount));
compressedCurrentLine.Append(lastChar);
lastChar = dataLine[i];
charRepeatCount = 1;
}
//process last char in dataLine
if (lastChar.Equals('0'))
{
//fills the line, to the right, with zeros
compressedCurrentLine.Append(',');
}
else if (lastChar.Equals('1'))
{
//fills the line, to the right, with ones
compressedCurrentLine.Append('!');
}
else
{
compressedCurrentLine.Append(GetZebraCharCount(charRepeatCount));
compressedCurrentLine.Append(lastChar);
}
if (compressedCurrentLine.Equals(compressedPreviousLine))
{
//previous line is repeated
compressedLines.Append(':');
}
else
{
compressedLines.Append(compressedCurrentLine);
}
compressedPreviousLine = compressedCurrentLine.ToString();
compressedCurrentLine.Clear();
}
return compressedLines.ToString();
}
/// <summary>
/// Uncompress data
/// </summary>
/// <param name="compressedHexData"></param>
/// <param name="bytesPerRow"></param>
/// <returns></returns>
public static string Uncompress(string compressedHexData, int bytesPerRow)
{
var hexData = new StringBuilder();
var chunkSize = bytesPerRow * 2;
var lineIndex = 0;
var totalCount = 0;
var reverseMapping = CompressionCountMapping.ToDictionary(o => o.Value, o => o.Key);
foreach (var c in compressedHexData)
{
if (c.Equals(':'))
{
var appendRepeat = hexData.ToString().Substring(hexData.Length - (chunkSize + 1));
hexData.Append(appendRepeat);
continue;
}
if (c.Equals(','))
{
var appendZero = new string('0', chunkSize - lineIndex);
hexData.Append(appendZero);
hexData.Append("\n");
lineIndex = 0;
continue;
}
if (c.Equals('!'))
{
var appendOne = new string('1', chunkSize - lineIndex);
hexData.Append(appendOne);
hexData.Append("\n");
lineIndex = 0;
continue;
}
if (reverseMapping.TryGetValue(c, out var count))
{
totalCount += count;
continue;
}
var append = new string(c, totalCount);
hexData.Append(append);
lineIndex += append.Length;
totalCount = 0;
if (lineIndex == chunkSize)
{
hexData.Append("\n");
lineIndex = 0;
}
}
return hexData.ToString();
}
public static string GetZebraCharCount(int charRepeatCount)
{
if (CompressionCountMapping.TryGetValue(charRepeatCount, out var compressionKey))
{
return $"{compressionKey}";
}
var compressionKeys = new StringBuilder(5);
var multi20 = charRepeatCount / MinCompressionBlockCount * MinCompressionBlockCount;
var remainder = charRepeatCount % multi20;
while (remainder > 0 || multi20 > 0)
{
if (multi20 > MaxCompressionRepeatCount)
{
remainder += multi20 - MaxCompressionRepeatCount;
multi20 = MaxCompressionRepeatCount;
}
if (!CompressionCountMapping.TryGetValue(multi20, out compressionKey))
{
throw new Exception("Compression failure");
}
compressionKeys.Append(compressionKey);
if (remainder == 0)
{
break;
}
if (remainder <= 20)
{
CompressionCountMapping.TryGetValue(remainder, out compressionKey);
compressionKeys.Append(compressionKey);
break;
}
multi20 = remainder / MinCompressionBlockCount * MinCompressionBlockCount;
remainder %= multi20;
}
return compressionKeys.ToString();
}
}
}