Skip to content

Commit

Permalink
Improved: Barcode scaling for PDF417 and Code128 (#219)
Browse files Browse the repository at this point in the history
* Improved: Code128 charset usage
Improved: PDF417 scaling
Improved: DHL uses a decimal height for PDF417
Improved: PDF417 size when no column value is given

* Improved: PDF417 uses correct vertical scaling now

* Improved: GS1 type support
Improved: Code128 invocation filtering
  • Loading branch information
daanggc authored Apr 23, 2024
1 parent 7c5407d commit 8ffae27
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 45 deletions.
4 changes: 4 additions & 0 deletions src/BinaryKits.Zpl.Label/Elements/ZplPDF417.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class ZplPDF417 : ZplPositionedElementBase, IFormatElement
{

public int Height { get; protected set; }
public int ModuleWidth { get; protected set; }
public string Content { get; protected set; }
public FieldOrientation FieldOrientation { get; protected set; }
public int? Columns { get; protected set; }
Expand All @@ -23,6 +24,7 @@ public class ZplPDF417 : ZplPositionedElementBase, IFormatElement
/// <param name="positionX"></param>
/// <param name="positionY"></param>
/// <param name="height"></param>
/// <param name="moduleWidth"></param>
/// <param name="columns">1-30: Number of data columns to encode. Default will auto balance 1:2 row to column</param>
/// <param name="rows">3-90. Number of data columns to encode. Default will auto balance 1:2 row to column</param>
/// <param name="compact">Truncate right row indicators and stop pattern</param>
Expand All @@ -34,6 +36,7 @@ public ZplPDF417(
int positionX,
int positionY,
int height = 8,
int moduleWidth = 2,
int? columns = null,
int? rows = null,
bool compact = false,
Expand All @@ -45,6 +48,7 @@ public ZplPDF417(
{
FieldOrientation = fieldOrientation;
Height = height;
ModuleWidth = moduleWidth;
Columns = columns;
Rows = rows;
Compact = compact;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,12 @@
<None Update="Labels\Test\TextRotation2-102x152.zpl2">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Labels\Test\BarcodePDF417-102x152.zpl2">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Labels\Test\GS1-102x152.zpl2">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
^XA

^FO10,10
^BY1
^B7N,4
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FX This one looks half-height on Zebra
^FO120,10
^BY1
^B7N,4,,,,Y
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FX Difference for security level
^FO10,90
^BY1
^B7N,4,3
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO120,90
^BY1
^B7N,4,4
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO250,90
^BY1
^B7N,4,5
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO400,90
^BY1
^B7N,4,6
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FX Labelary is wrong on this one
^FO570,90
^BY1
^B7N,4,6,,,Y
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO10,210
^BY1
^B7N,4,5,6
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO210,210
^BY1
^B7N,4,5,6,20
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FX Difference for missing columns
^FO10,300
^BY1
^B7N,4,5,8,24,N
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO250,300
^BY1
^B7N,4,5,,24,N
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FX DHL real-world test vs corrected
^FO10,400
^BY2
^B7N,6.7,5,,30,N
^FD
UNH+2876965+IFTMIN:D:96B:UN+DHL5.6.0/AWESOME'BGM+787+999994760001+9'DTM+186:20240312:102'TSR+++10'TOD+Z02++CPT'NAD+OS+0464651'NAD+CN+++MEVR. SOME PERSONE+STREET 17 E+SOMEPLACE++0+DE'CTA+GR'[email protected]:EM'GID+0+1'MEA+WT++KGM:2'PCI+ZZ1+JVGL09999999999994760001'UNT+13+123546798'
^FS

^FX Difference in blocks between all three sources. (This, Zebra and Labelary)
^FO10,590
^BY2
^B7N,6,5,9,30,N
^FD
UNH+2876965+IFTMIN:D:96B:UN+DHL5.6.0/AWESOME'BGM+787+999994760001+9'DTM+186:20240312:102'TSR+++10'TOD+Z02++CPT'NAD+OS+0464651'NAD+CN+++MEVR. SOME PERSONE+STREET 17 E+SOMEPLACE++0+DE'CTA+GR'[email protected]:EM'GID+0+1'MEA+WT++KGM:2'PCI+ZZ1+JVGL09999999999994760001'UNT+13+123546798'
^FS

^FX Difference in blocks with Labelary but not with Zebra printed result
^FO10,780
^BY2
^B7N,4,5,6
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO380,780
^BY2
^B7N,4,5,6
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789^FS

^FO10,840
^BY3
^B7N,4,5,8,24,N
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO10,950
^BY4
^B7N,4,6,7,24,N
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^FO10,1060
^BY5
^B7N,4,6,4,36,N
^FDABCDEFGHIJKLMNOPQRSTUVWXYZ^FS

^XZ
18 changes: 18 additions & 0 deletions src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/GS1-102x152.zpl2
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
^XA

^FX all should produce 011234567890123121123456

^FT101,59^A0N,31,30^FH\^CI28^FDGS1-128 : ^FS^CI27
^BY2,3,54^FT110,134^BCN,,N,N
^FH\^FD>;>8011234567890123121123456^FS

^FT101,174^A0N,31,30^FH\^CI28^FDGS1-DataMatrix : ^FS^CI27
^FT185,324^BXN,6,200,0,0,1,_,1
^FH\^FD_1011234567890123121123456^FS

^FT548,174^A0N,31,30^FH\^CI28^FDGS1-QR: ^FS^CI27
^FO642,206^BQN,2,5
^FH\^FD>;>8011234567890123121123456^FS

^PQ1,0,1,Y
^XZ
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public override ZplElementBase Analyze(string zplCommand)
}
if (this.VirtualPrinter.NextElementFieldData is PDF417FieldData pdf147)
{
return new ZplPDF417(text, x, y, pdf147.Height, pdf147.Columns, pdf147.Rows, pdf147.Compact, pdf147.SecurityLevel, pdf147.FieldOrientation, bottomToTop);
return new ZplPDF417(text, x, y, pdf147.Height, moduleWidth, pdf147.Columns, pdf147.Rows, pdf147.Compact, pdf147.SecurityLevel, pdf147.FieldOrientation, bottomToTop);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using BinaryKits.Zpl.Label.Elements;
using BinaryKits.Zpl.Viewer.Models;
using System;
using System.Globalization;

namespace BinaryKits.Zpl.Viewer.CommandAnalyzers
{
Expand Down Expand Up @@ -33,6 +34,16 @@ public override ZplElementBase Analyze(string zplCommand)
{
height = tmpint;
}
else if (zplDataParts.Length > 1)
{
//Sometimes a decimal is given, this works around that.
var tempDecimal = Convert.ToDecimal(zplDataParts[1], new CultureInfo("en-US"));
var tempHeight = (int)Math.Floor(tempDecimal);
if (tempHeight > 0)
{
height = tempHeight;
}
}

int securityLevel = 0;
if (zplDataParts.Length > 2 && int.TryParse(zplDataParts[2], out tmpint))
Expand Down
55 changes: 28 additions & 27 deletions src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode128ElementDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,19 @@
using BinaryKits.Zpl.Viewer.Helpers;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text.RegularExpressions;

namespace BinaryKits.Zpl.Viewer.ElementDrawers
{
public class Barcode128ElementDrawer : BarcodeDrawerBase
{

/// <summary>
/// Start sequence lookups.
/// <see href="https://supportcommunity.zebra.com/s/article/Creating-GS1-Barcodes-with-Zebra-Printers-for-Data-Matrix-and-Code-128-using-ZPL"/>
/// </summary>
private static readonly Dictionary<string, TYPE> startCodeMap = new Dictionary<string, TYPE>()
{
{ ">9", TYPE.CODE128A },
{ ">:", TYPE.CODE128B },
{ ">;", TYPE.CODE128C }
};

private static readonly Regex startCodeRegex = new Regex(@"^(>[9:;])(.+)$", RegexOptions.Compiled);
private static readonly Regex invalidInvocationRegex = new Regex(@"(?<!^)>[9:;]", RegexOptions.Compiled);
private static readonly Regex startCodeRegex = new Regex(@"(>[9:;])", RegexOptions.Compiled);
private static readonly Regex invalidInvocationRegex = new Regex(@"(?<!^)>[<0=12345679:;]", RegexOptions.Compiled); //>8 has limited support here

// As defined in BarcodeLib.Symbologies.Code128
private static readonly string FNC1 = Convert.ToChar(200).ToString();
Expand All @@ -46,40 +37,50 @@ public override void Draw(ZplElementBase element, DrawerOptions options)
{
if (element is ZplBarcode128 barcode)
{
var barcodeType = TYPE.CODE128B;
// remove any start sequences not at the start of the content (invalid invocation)
string content = invalidInvocationRegex.Replace(barcode.Content, "");
var barcodeType = TYPE.CODE128;

//remove the start code form the content we only support the globals N,A,D,U and our barcode library doesn't support these types
string content = startCodeRegex.Replace(barcode.Content, "");
string interpretation = content;

// remove any start sequences not at the start of the content (invalid invocation)
content = invalidInvocationRegex.Replace(content, "");
interpretation = content;

// support hand-rolled GS1
content = content.Replace(">8", FNC1);
interpretation = interpretation.Replace(">8", "");

if (string.IsNullOrEmpty(barcode.Mode) || barcode.Mode == "N")
{
Match startCodeMatch = startCodeRegex.Match(content);
if (startCodeMatch.Success)
{
barcodeType = startCodeMap[startCodeMatch.Groups[1].Value];
content = startCodeMatch.Groups[2].Value;
interpretation = content;
}
// support hand-rolled GS1
content = content.Replace(">8", FNC1);
interpretation = interpretation.Replace(">8", "");
// TODO: support remaining escapes within a barcode
barcodeType = TYPE.CODE128; // dynamic
//TODO: Instead of using the auto type, switch type for each part of the content
// - Current library doesn't support that.
//>:+B210AC>50270>6/$+2>5023080000582>6L
//[TYPE.CODE128B]+B210AC
//[TYPE.CODE128C]0270
//[TYPE.CODE128B]+/$+2
//[TYPE.CODE128C]023080000582
//[TYPE.CODE128B]L
}
else if (barcode.Mode == "A")
{
//A (automatic mode, the ZPL engine automatically determines the subsets that are used to encode the data)
barcodeType = TYPE.CODE128; // dynamic
}
else if (barcode.Mode == "D")
{
//D (UCC/EAN mode, field data must contain GS1 numbers)
barcodeType = TYPE.CODE128C;
content = content.Replace(">8", FNC1);
interpretation = interpretation.Replace(">8", "");

if (!content.StartsWith(FNC1))
{
content = FNC1 + content;
}
}
else if (barcode.Mode == "U")
{
//U (UCC case mode, field data must contain 19 digits)
barcodeType = TYPE.CODE128C;
content = content.PadLeft(19, '0').Substring(0, 19);
int checksum = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using BinaryKits.Zpl.Label.Elements;
using SkiaSharp;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using ZXing;
using ZXing.Datamatrix;
using ZXing.Datamatrix.Encoder;
Expand Down Expand Up @@ -29,11 +30,22 @@ public override void Draw(ZplElementBase element)
float x = dataMatrix.PositionX;
float y = dataMatrix.PositionY;

// support hand-rolled GS1
bool gs1Mode = false;
var content = dataMatrix.Content;
if (Regex.Match(content, @"(^_1)", RegexOptions.None).Success)
{
content = Regex.Replace(content, @"(^_1)", "");
gs1Mode = true;
}

var writer = new DataMatrixWriter();
var hints = new Dictionary<EncodeHintType, object> {
{ EncodeHintType.DATA_MATRIX_SHAPE, SymbolShapeHint.FORCE_SQUARE }
{ EncodeHintType.DATA_MATRIX_SHAPE, SymbolShapeHint.FORCE_SQUARE },
{ EncodeHintType.DATA_MATRIX_COMPACT, gs1Mode },
{ EncodeHintType.GS1_FORMAT, gs1Mode }
};
var result = writer.encode(dataMatrix.Content, BarcodeFormat.DATA_MATRIX, 0, 0, hints);
var result = writer.encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, hints);

using var resizedImage = this.BitMatrixToSKBitmap(result, dataMatrix.Height);
{
Expand Down
35 changes: 22 additions & 13 deletions src/BinaryKits.Zpl.Viewer/ElementDrawers/PDF417ElementDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,26 +56,37 @@ public override void Draw(ZplElementBase element)
{
mincols = 1;
maxcols = 30;

if (pdf417.Rows != null)
{
//When column count isn't defined, and rows count is,
// the col/row ratio is calculated using the algorithm in: ZXing.PDF417.Internal.PDF417.determineDimensions
// to allow a range for that algorithm, we divide the given row amount by 2.
// as the algorithm goes from highest to lowest, this usually produces an acceptable result
minrows /= 2;
}
}
var writer = new PDF417Writer();
var hints = new Dictionary<EncodeHintType, object> {
{ EncodeHintType.CHARACTER_SET, "UTF-8" },
// { EncodeHintType.CHARACTER_SET, "ISO-8859-1" },
{ EncodeHintType.PDF417_COMPACT, pdf417.Compact },
//{ EncodeHintType.PDF417_AUTO_ECI, true },
//{ EncodeHintType.DISABLE_ECI, true },
{ EncodeHintType.PDF417_COMPACTION, Compaction.AUTO},
{ EncodeHintType.PDF417_ASPECT_RATIO, 3 }, // height of a single bar relative to width
{ EncodeHintType.PDF417_IMAGE_ASPECT_RATIO, 2.0f }, // zpl default, proportions of columns to rows
{ EncodeHintType.PDF417_ASPECT_RATIO, PDF417AspectRatio.A3 }, // height of a single bar relative to width
{ EncodeHintType.PDF417_IMAGE_ASPECT_RATIO, 1.0f }, // zpl default 2.0f, proportions of columns to rows //1.0f looks closer to printed Zebra label
{ EncodeHintType.MARGIN, 0 }, // its an int
{ EncodeHintType.ERROR_CORRECTION, ConvertErrorCorrection(pdf417.SecurityLevel) },
{ EncodeHintType.PDF417_DIMENSIONS, new Dimensions(mincols, maxcols, minrows, maxrows) },
};

var default_bitmatrix = writer.encode(pdf417.Content, BarcodeFormat.PDF_417, 0, 0, hints);

var upscaled = proportional_upscale(default_bitmatrix, 3);
var result = vertical_scale(upscaled, pdf417.Height);


//PDF417_ASPECT_RATIO set to 3, we need to multiply that with pdf417.ModuleWidth (defined by ^BY)
var bar_height = pdf417.ModuleWidth * 3;
var upscaled = proportional_upscale(default_bitmatrix, pdf417.ModuleWidth);
var result = vertical_scale(upscaled, pdf417.Height, bar_height);

using var resizedImage = this.BitMatrixToSKBitmap(result, 1);
{
var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray();
Expand Down Expand Up @@ -107,14 +118,12 @@ private BitMatrix proportional_upscale(BitMatrix old, int scale) {
return upscaled;
}

// needed to match labelary
// needed to match zebra and labelary
// zebra assumptions:
// - we can only set the height in zpl in points, not the width
// - each bar is 3 "points" thick, until ^BY is implemented
// - because we have PDF417_ASPECT_RATIO set to 3, and upscaling to 3, the height of a single bar is now 9
// we only need to scale to the bar height/9
private BitMatrix vertical_scale(BitMatrix old_matrix, int new_bar_height) {
int old_bar_height = 9;
// - each bar is ^BY "points" thick
// - because we have PDF417_ASPECT_RATIO set to 3, the height of a single bar is now 3 * ^BY
private BitMatrix vertical_scale(BitMatrix old_matrix, int new_bar_height, int old_bar_height) {
int width = old_matrix.Width;
int rows = old_matrix.Height / old_bar_height; // logical rows;

Expand Down
Loading

0 comments on commit 8ffae27

Please sign in to comment.