From 5c7233704e5186890c29b9c232074310bf6a3427 Mon Sep 17 00:00:00 2001 From: primo-ppcg Date: Fri, 26 Apr 2024 11:17:06 +0700 Subject: [PATCH] Merge develop branch (#232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create dotnet - test.yml * Bump SkiaSharp from 2.88.3 to 2.88.6 in /src/BinaryKits.Zpl.Viewer Bumps [SkiaSharp](https://github.com/mono/SkiaSharp) from 2.88.3 to 2.88.6. - [Release notes](https://github.com/mono/SkiaSharp/releases) - [Commits](https://github.com/mono/SkiaSharp/compare/v2.88.3...v2.88.6) --- updated-dependencies: - dependency-name: SkiaSharp dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix(viewer): cannot find package vue@next (#208) * fix(viewer): cannot find package vue@next * Update index.html Don't use specific versions for web dependencies. * Enable ^FV command (needed for UPS) (#195) * Enable ^FV command - Based off ^FD * ZPL Parser: Enable ignoring certain commands (#194) * Enable ignoring certain commands - small refactors + comments in the zpl parser * Comment specification * Add documentation on how to add barcode support (#198) * Change invertdraw to skia xor blending (#193) - Also added tests - Refactored viewer tests * Structure Viewer UnitTest project (merge after #193) (#199) * Change invertdraw to skia xor blending - Also added tests - Refactored viewer tests * Begin structuring viewer testing project - readme for goals - custom test for feature development - zpl/png data folder copied to output - factor out more to common * Try to fix cannot copy data to output for linux build * Copy data again * Capitalization mistake * Added: Font fallback structure for the default fonts. (#217) * Added: Font fallback structure for the default fonts. * Improved: DejaVu Sans Mono as first mono type Improved: Courier New is more often available than Courier * Added: Roboto font to Dockerfile * Improved: Enabled antialiased text (#218) * Improved: Barcode scaling for PDF417 and Code128 (#219) * 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 * Added: PDF generation (#220) * Added: Font fallback structure for the default fonts. * Improved: DejaVu Sans Mono as first mono type Improved: Courier New is more often available than Courier * Improved: Enabled antialiased text * Improved: Code128 charset usage Improved: PDF417 scaling Improved: DHL uses a decimal height for PDF417 Improved: PDF417 size when no column value is given * Added: Roboto fontface to docker image * Improved: Added a working roboto font to the Dockerfile * Improved: PDF417 uses correct vertical scaling now * Added: PDF support Added: Download buttons for ZPL, PNG and PDF Fixed: GraphicBox radius with thicker lines Improved: Invert/Reverse draw results when using white elements Improved: White background for PNG requested from viewer * Added: More real world tests Improved: Tabs for tests and examples in webinterface * Improved: GraphicBox can't have a border smaller than 1 * Improved: Made more ^FR blending native for performance * Improved: GraphicBox border should not exceed half of the smallest side. Improved: Adjusted some example sizes * Fixed: Build issues * Improved: More Skia blending to improve performance Improved: Performance of GraphicBox rounding fix Added: GraphicCircle supports reverse draw now Improved: Overlay can handle custom label sizes better now * Improved: Overlay requests may use wrong number formatting * Bump Dependencies Bump ImageSharp and SkiaSharp dependencies. * Added: MaxiCode support (#224) * Added: MaxiCode support * Improved: Slight finetune on maxicode linux fix * Patch #194 (#227) Don't neglect to append on ignore. * Added: Barcode93 support (#226) * Added: Barcode93 support * Update Code93 Update interpretation line Add test to WebApi * Fix vue lib URL (#228) * Drop BarcodeLib dependency (#229) * Drop BarcodeLib dependency * don't inline regex * Prepare for release * Silence warnings --------- Signed-off-by: dependabot[bot] Co-authored-by: Yiping Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dmitrii Savchenkov Co-authored-by: reportingissue <136993729+reportingissue@users.noreply.github.com> Co-authored-by: Daniël van der Garde <12891775+daanggc@users.noreply.github.com> Co-authored-by: Marius Starke <72494359+marius-arch@users.noreply.github.com> Co-authored-by: Yiping --- .github/workflows/dotnet - test.yml | 50 +++ .../BarcodeTest.cs | 16 + .../BinaryKits.Zpl.Label.csproj | 4 +- .../Elements/ZplBarcode93.cs | 69 ++++ .../Elements/ZplGraphicCircle.cs | 3 +- .../Elements/ZplMaxiCode.cs | 85 ++++ .../Elements/ZplPDF417.cs | 4 + .../BinaryKits.Zpl.Labelary.csproj | 2 +- src/BinaryKits.Zpl.Labelary/LabelSize.cs | 8 +- src/BinaryKits.Zpl.Labelary/LabelaryClient.cs | 9 +- .../BinaryKits.Zpl.Protocol.csproj | 4 +- .../ImageSharpImageConverter.cs | 9 +- src/BinaryKits.Zpl.TestConsole/Program.cs | 11 +- .../BinaryKits.Zpl.Viewer.UnitTest.csproj | 8 +- src/BinaryKits.Zpl.Viewer.UnitTest/Common.cs | 53 +++ .../CustomTest.cs | 15 + .../Data/Png/Ref/.gitkeep | 0 .../Data/Png/Test/.gitkeep | 0 .../Data/Zpl/custom.zpl2 | 0 .../Data/Zpl/font-assign.zpl2 | 6 + .../Data/Zpl/invert1.zpl2 | 14 + .../Data/Zpl/invert2.zpl2 | 46 +++ .../Data/Zpl/merge.zpl2 | 21 + .../DrawerTest.cs | 72 +--- src/BinaryKits.Zpl.Viewer.UnitTest/README.md | 23 ++ .../BinaryKits.Zpl.Viewer.WebApi.csproj | 30 +- .../Controllers/ViewerController.cs | 58 ++- .../Labels/Example/Example10-102x152.zpl2 | 109 +++++ .../Labels/Example/Example11-102x152.zpl2 | 102 +++++ .../Labels/Example/Example12-102x152.zpl2 | 110 +++++ ...mple3-102x152.zpl2 => Example3-54x86.zpl2} | 0 ...ple5-102x152.zpl2 => Example5-75x202.zpl2} | 0 ...ple6-102x152.zpl2 => Example6-75x254.zpl2} | 0 ...le7-102x152.zpl2 => Example7-178x152.zpl2} | 0 ...ple8-102x152.zpl2 => Example8-64x152.zpl2} | 0 .../Labels/Test/Barcode128-102x152.zpl2 | 12 +- .../Labels/Test/Barcode93-102x152.zpl2 | 18 + .../Labels/Test/BarcodeEAN13-102x152.zpl2 | 22 +- .../Labels/Test/BarcodePDF417-102x152.zpl2 | 104 +++++ .../Labels/Test/FieldReversePrint3-54x86.zpl2 | 107 +++++ .../Labels/Test/GS1-102x152.zpl2 | 18 + .../Labels/Test/MaxiCode-102x152.zpl2 | 27 ++ .../Models/RenderLabelDto.cs | 1 + .../Models/RenderRequestDto.cs | 4 + .../Models/RenderResponseDto.cs | 1 + .../wwwroot/index.html | 149 ++++++- .../BinaryKits.Zpl.Viewer.csproj | 13 +- .../Code93BarcodeZplCommandAnalyzer.cs | 55 +++ .../FieldDataZplCommandAnalyzer.cs | 15 +- .../FieldHexadecimalZplCommandAnalyzer.cs | 1 + .../FieldSeparatorZplCommandAnalyzer.cs | 1 + .../FieldVarialbleZplCommandAnalyzer.cs | 14 + .../GraphicCircleZplCommandAnalyzer.cs | 4 +- .../MaxiCodeBarcodeZplCommandAnalyzer.cs | 43 ++ .../PDF417BarcodeCommandAnalyzer.cs | 11 + .../ElementDrawers/Barcode128ElementDrawer.cs | 98 ++--- .../ElementDrawers/Barcode39ElementDrawer.cs | 48 +-- .../ElementDrawers/Barcode93ElementDrawer.cs | 49 +++ .../ElementDrawers/BarcodeDrawerBase.cs | 187 ++++++--- .../BarcodeEAN13ElementDrawer.cs | 119 ++++-- .../ElementDrawers/DataMatrixElementDrawer.cs | 30 +- .../ElementDrawers/DrawerOptions.cs | 101 ++++- .../ElementDrawers/ElementDrawerBase.cs | 17 +- .../ElementDrawers/FieldBlockElementDrawer.cs | 27 +- .../ElementDrawers/GraphicBoxElementDrawer.cs | 129 ++++-- .../GraphicCircleElementDrawer.cs | 45 +- .../GraphicFieldElementDrawer.cs | 4 +- .../ElementDrawers/IElementDrawer.cs | 21 +- .../ElementDrawers/ImageMoveElementDrawer.cs | 3 + .../Interleaved2of5BarcodeDrawer.cs | 43 +- .../ElementDrawers/MaxiCodeElementDrawer.cs | 161 ++++++++ .../ElementDrawers/PDF417ElementDrawer.cs | 37 +- .../ElementDrawers/QrCodeElementDrawer.cs | 26 +- .../RecallGraphicElementDrawer.cs | 3 + .../ElementDrawers/TextFieldElementDrawer.cs | 29 +- .../Helpers/FontHelper.cs | 28 -- .../Models/Code93BarcodeFieldData.cs | 13 + .../Models/MaxiCodeBarcodeFieldData.cs | 12 + src/BinaryKits.Zpl.Viewer/README.md | 83 +++- .../Symologies/ZplCode128Symbology.cs | 391 ++++++++++++++++++ src/BinaryKits.Zpl.Viewer/VirtualPrinter.cs | 11 + src/BinaryKits.Zpl.Viewer/ZplAnalyzer.cs | 33 +- src/BinaryKits.Zpl.Viewer/ZplElementDrawer.cs | 238 ++++++++--- src/Dockerfile | 1 + 84 files changed, 3028 insertions(+), 520 deletions(-) create mode 100644 .github/workflows/dotnet - test.yml create mode 100644 src/BinaryKits.Zpl.Label/Elements/ZplBarcode93.cs create mode 100644 src/BinaryKits.Zpl.Label/Elements/ZplMaxiCode.cs create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Common.cs create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/CustomTest.cs create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Data/Png/Ref/.gitkeep create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Data/Png/Test/.gitkeep create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/custom.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/font-assign.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert1.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert2.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/merge.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.UnitTest/README.md create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example10-102x152.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example11-102x152.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example12-102x152.zpl2 rename src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/{Example3-102x152.zpl2 => Example3-54x86.zpl2} (100%) rename src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/{Example5-102x152.zpl2 => Example5-75x202.zpl2} (100%) rename src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/{Example6-102x152.zpl2 => Example6-75x254.zpl2} (100%) rename src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/{Example7-102x152.zpl2 => Example7-178x152.zpl2} (100%) rename src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/{Example8-102x152.zpl2 => Example8-64x152.zpl2} (100%) create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode93-102x152.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodePDF417-102x152.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/FieldReversePrint3-54x86.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/GS1-102x152.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/MaxiCode-102x152.zpl2 create mode 100644 src/BinaryKits.Zpl.Viewer/CommandAnalyzers/Code93BarcodeZplCommandAnalyzer.cs create mode 100644 src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldVarialbleZplCommandAnalyzer.cs create mode 100644 src/BinaryKits.Zpl.Viewer/CommandAnalyzers/MaxiCodeBarcodeZplCommandAnalyzer.cs create mode 100644 src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode93ElementDrawer.cs create mode 100644 src/BinaryKits.Zpl.Viewer/ElementDrawers/MaxiCodeElementDrawer.cs delete mode 100644 src/BinaryKits.Zpl.Viewer/Helpers/FontHelper.cs create mode 100644 src/BinaryKits.Zpl.Viewer/Models/Code93BarcodeFieldData.cs create mode 100644 src/BinaryKits.Zpl.Viewer/Models/MaxiCodeBarcodeFieldData.cs create mode 100644 src/BinaryKits.Zpl.Viewer/Symologies/ZplCode128Symbology.cs diff --git a/.github/workflows/dotnet - test.yml b/.github/workflows/dotnet - test.yml new file mode 100644 index 00000000..b6101dda --- /dev/null +++ b/.github/workflows/dotnet - test.yml @@ -0,0 +1,50 @@ +name: .NET + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +jobs: + build-windows: + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET 6.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + working-directory: ./src + run: dotnet restore + - name: Build + working-directory: ./src + run: dotnet build --configuration Release --no-restore /p:NoWarn=1591 + - name: Test + working-directory: ./src + run: dotnet test --configuration Release --no-restore --no-build --verbosity normal + + build-linux: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET 6.0 + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 6.0.x + - name: Restore dependencies + working-directory: ./src + run: dotnet restore + - name: Build + working-directory: ./src + run: dotnet build --configuration Release --no-restore /p:NoWarn=1591 + - name: Test + working-directory: ./src + run: dotnet test --configuration Release --no-restore --no-build --verbosity normal + + diff --git a/src/BinaryKits.Zpl.Label.UnitTest/BarcodeTest.cs b/src/BinaryKits.Zpl.Label.UnitTest/BarcodeTest.cs index ff080acd..f6a33f9b 100644 --- a/src/BinaryKits.Zpl.Label.UnitTest/BarcodeTest.cs +++ b/src/BinaryKits.Zpl.Label.UnitTest/BarcodeTest.cs @@ -24,6 +24,22 @@ public void Barcode39() Assert.AreEqual("^XA\n^LH0,0\n^CI28\n\n^FO100,100\n^BY2,3\n^B3N,N,100,Y,N\n^FD123ABC^FS\n^XZ", output); } + [TestMethod] + public void Barcode93() + { + var elements = new List + { + new ZplBarcode93("123ABC", 100, 300) + }; + + var renderEngine = new ZplEngine(elements); + var output = renderEngine.ToZplString(new ZplRenderOptions { AddEmptyLineBeforeElementStart = true }); + + Debug.WriteLine(output); + Assert.IsNotNull(output); + Assert.AreEqual("^XA\n^LH0,0\n^CI28\n\n^FO100,300\n^BY2,3\n^BAN,100,Y,N,N\n^FD123ABC^FS\n^XZ", output); + } + [TestMethod] public void Barcode128() { diff --git a/src/BinaryKits.Zpl.Label/BinaryKits.Zpl.Label.csproj b/src/BinaryKits.Zpl.Label/BinaryKits.Zpl.Label.csproj index 4b0d6e6b..e27d4b6d 100644 --- a/src/BinaryKits.Zpl.Label/BinaryKits.Zpl.Label.csproj +++ b/src/BinaryKits.Zpl.Label/BinaryKits.Zpl.Label.csproj @@ -4,7 +4,7 @@ net472;netstandard2.0;net6.0 This package allows you to simply and reliably prepare labels complying with the Zebra programming language, using predefined class/typing. It also supports you with image conversion. Binary Kits Pte. Ltd. - 3.1.5 + 3.2.0 Binary Kits Pte. Ltd. Zebra ZPL ZPL2 Printer Label @@ -23,7 +23,7 @@ - 2.1.3 + 2.1.8 diff --git a/src/BinaryKits.Zpl.Label/Elements/ZplBarcode93.cs b/src/BinaryKits.Zpl.Label/Elements/ZplBarcode93.cs new file mode 100644 index 00000000..9aae382d --- /dev/null +++ b/src/BinaryKits.Zpl.Label/Elements/ZplBarcode93.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace BinaryKits.Zpl.Label.Elements +{ + /// + /// Code 93 Barcode + /// + public class ZplBarcode93 : ZplBarcode + { + public bool CheckDigit { get; private set; } + + /// + /// Code 93 Barcode + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public ZplBarcode93( + string content, + int positionX, + int positionY, + int height = 100, + int moduleWidth = 2, + double wideBarToNarrowBarWidthRatio = 3, + FieldOrientation fieldOrientation = FieldOrientation.Normal, + bool printInterpretationLine = true, + bool printInterpretationLineAboveCode = false, + bool checkDigit = false, + bool bottomToTop = false) + : base(content, + positionX, + positionY, + height, + moduleWidth, + wideBarToNarrowBarWidthRatio, + fieldOrientation, + printInterpretationLine, + printInterpretationLineAboveCode, + bottomToTop) + { + this.CheckDigit = checkDigit; + } + + /// + public override IEnumerable Render(ZplRenderOptions context) + { + //TODO:Add 'mode' + + //^FO100,100 ^ BY3 + //^BAN,100,Y,N,N + //^FD123456 ^ FS + var result = new List(); + result.AddRange(RenderPosition(context)); + result.Add(RenderModuleWidth()); + result.Add($"^BA{RenderFieldOrientation()},{context.Scale(Height)},{RenderPrintInterpretationLine()},{RenderPrintInterpretationLineAboveCode()},{(CheckDigit ? "Y" : "N")}"); + result.Add($"^FD{Content}^FS"); + + return result; + } + } +} diff --git a/src/BinaryKits.Zpl.Label/Elements/ZplGraphicCircle.cs b/src/BinaryKits.Zpl.Label/Elements/ZplGraphicCircle.cs index f1c5f915..5a20fe0b 100644 --- a/src/BinaryKits.Zpl.Label/Elements/ZplGraphicCircle.cs +++ b/src/BinaryKits.Zpl.Label/Elements/ZplGraphicCircle.cs @@ -24,8 +24,9 @@ public ZplGraphicCircle( int diameter, int borderThickness = 1, LineColor lineColor = LineColor.Black, + bool reversePrint = false, bool bottomToTop = false) - : base(positionX, positionY, borderThickness, lineColor, bottomToTop: bottomToTop) + : base(positionX, positionY, borderThickness, lineColor, reversePrint, bottomToTop) { Diameter = diameter; } diff --git a/src/BinaryKits.Zpl.Label/Elements/ZplMaxiCode.cs b/src/BinaryKits.Zpl.Label/Elements/ZplMaxiCode.cs new file mode 100644 index 00000000..41cb728d --- /dev/null +++ b/src/BinaryKits.Zpl.Label/Elements/ZplMaxiCode.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using System.Text; + +namespace BinaryKits.Zpl.Label.Elements +{ + public class ZplMaxiCode : ZplPositionedElementBase, IFormatElement + { + public string Content { get; protected set; } + + public int Mode { get; private set; } + + public int Position { get; private set; } + + public int Total { get; private set; } + + public bool UseHexadecimalIndicator { get; protected set; } + + /// + /// Zpl QrCode + /// + /// + /// + /// + /// 2 (numeric postal code) Default, 3 (alphanumeric postal code), 4 (standard), 5 (full EEC), and 6 (reader programming) + /// 1-8, (default: 1) + /// 1-8, (default: 1) + /// + /// + public ZplMaxiCode( + string content, + int positionX, + int positionY, + int mode = 2, + int position = 1, + int total = 1, + bool useHexadecimalIndicator = false, + bool bottomToTop = false) + : base(positionX, positionY, bottomToTop) + { + Content = content; + Mode = mode; + Position = position; + Total = total; + UseHexadecimalIndicator = useHexadecimalIndicator; + } + + /// + public override IEnumerable Render(ZplRenderOptions context) + { + //^FO100,100 + //^BD2,1,1 + //^FH^FD002840100450000_5B)>_1E01_1D961Z00136071_1DUPSN_1D123X56_1D028_1D_1D001/001_1D011_1DN_1D_1DNEW YORK_1DNY_1E_04^FS + var result = new List(); + result.AddRange(RenderPosition(context)); + result.Add($"^BD{Mode},{Position},{Total}"); + result.Add(RenderFieldDataSection()); + + return result; + } + + protected string RenderFieldDataSection() + { + var sb = new StringBuilder(); + if (UseHexadecimalIndicator) + { + sb.Append("^FH"); + } + + if (Content != null) + { + sb.Append("^FD"); + sb.Append(Content); + sb.Append("^FS"); + } + + return sb.ToString(); + } + + /// + public void SetTemplateContent(string content) + { + Content = content; + } + } +} diff --git a/src/BinaryKits.Zpl.Label/Elements/ZplPDF417.cs b/src/BinaryKits.Zpl.Label/Elements/ZplPDF417.cs index 74142c9b..41a231e7 100644 --- a/src/BinaryKits.Zpl.Label/Elements/ZplPDF417.cs +++ b/src/BinaryKits.Zpl.Label/Elements/ZplPDF417.cs @@ -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; } @@ -23,6 +24,7 @@ public class ZplPDF417 : ZplPositionedElementBase, IFormatElement /// /// /// + /// /// 1-30: Number of data columns to encode. Default will auto balance 1:2 row to column /// 3-90. Number of data columns to encode. Default will auto balance 1:2 row to column /// Truncate right row indicators and stop pattern @@ -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, @@ -45,6 +48,7 @@ public ZplPDF417( { FieldOrientation = fieldOrientation; Height = height; + ModuleWidth = moduleWidth; Columns = columns; Rows = rows; Compact = compact; diff --git a/src/BinaryKits.Zpl.Labelary/BinaryKits.Zpl.Labelary.csproj b/src/BinaryKits.Zpl.Labelary/BinaryKits.Zpl.Labelary.csproj index 1f0c2407..caabeac3 100644 --- a/src/BinaryKits.Zpl.Labelary/BinaryKits.Zpl.Labelary.csproj +++ b/src/BinaryKits.Zpl.Labelary/BinaryKits.Zpl.Labelary.csproj @@ -4,7 +4,7 @@ netstandard2.1 This package provides a client for the Labelary API for rendering ZPL data Binary Kits Pte. Ltd. - 1.0.1 + 1.0.2 Binary Kits Pte. Ltd. Zebra ZPL ZPL2 ZPLEmulator ZPLVirtualPrinter ZPLViewer Labelary diff --git a/src/BinaryKits.Zpl.Labelary/LabelSize.cs b/src/BinaryKits.Zpl.Labelary/LabelSize.cs index 239e19ce..87501bd4 100644 --- a/src/BinaryKits.Zpl.Labelary/LabelSize.cs +++ b/src/BinaryKits.Zpl.Labelary/LabelSize.cs @@ -25,7 +25,8 @@ public double WidthInInch return _width; } - return Math.Round(_width / _millimeterToInch, 0); + var rounded = Math.Round(_width / _millimeterToInch, 8); + return rounded; } } @@ -37,8 +38,9 @@ public double HeightInInch { return _height; } - - return Math.Round(_height / _millimeterToInch, 0); + + var rounded = Math.Round(_height / _millimeterToInch, 8); + return rounded; } } } diff --git a/src/BinaryKits.Zpl.Labelary/LabelaryClient.cs b/src/BinaryKits.Zpl.Labelary/LabelaryClient.cs index 4224e7f5..eb94965d 100644 --- a/src/BinaryKits.Zpl.Labelary/LabelaryClient.cs +++ b/src/BinaryKits.Zpl.Labelary/LabelaryClient.cs @@ -1,5 +1,6 @@ using Microsoft.Extensions.Logging; using System; +using System.Globalization; using System.Net.Http; using System.Text; using System.Threading.Tasks; @@ -42,9 +43,15 @@ public async Task GetPreviewAsync( { var dpi = printDensity.ToString().Substring(2); var zpl = Encoding.UTF8.GetBytes(zplData); + + //without setting this, a comma separator might be used for for the size numbers in the URL + var specifier = "G"; + var culture = CultureInfo.CreateSpecificCulture("en-US"); + var width = labelSize.WidthInInch.ToString(specifier, culture); + var height = labelSize.HeightInInch.ToString(specifier, culture); using var byteContent = new ByteArrayContent(zpl); - using (var response = await _httpClient.PostAsync($"{_apiEndpoint}/{dpi}/labels/{labelSize.WidthInInch}x{labelSize.HeightInInch}/0/", byteContent)) + using (var response = await _httpClient.PostAsync($"{_apiEndpoint}/{dpi}/labels/{width}x{height}/0/", byteContent)) { if (!response.IsSuccessStatusCode) { diff --git a/src/BinaryKits.Zpl.Protocol/BinaryKits.Zpl.Protocol.csproj b/src/BinaryKits.Zpl.Protocol/BinaryKits.Zpl.Protocol.csproj index 7ffbca2b..662f4928 100644 --- a/src/BinaryKits.Zpl.Protocol/BinaryKits.Zpl.Protocol.csproj +++ b/src/BinaryKits.Zpl.Protocol/BinaryKits.Zpl.Protocol.csproj @@ -4,7 +4,7 @@ net472;netstandard2.0;net6.0 This package contains the zebra protocol of the Zebra Programming Language. It also supports you with image conversion. Binary Kits Pte. Ltd. - 1.0.0 + 1.0.1 Binary Kits Pte. Ltd. Zebra ZPL ZPL2 Printer Protocol @@ -23,7 +23,7 @@ - 1.0.4 + 2.1.8 diff --git a/src/BinaryKits.Zpl.Protocol/ImageConverters/ImageSharpImageConverter.cs b/src/BinaryKits.Zpl.Protocol/ImageConverters/ImageSharpImageConverter.cs index 960a92ab..57be227a 100644 --- a/src/BinaryKits.Zpl.Protocol/ImageConverters/ImageSharpImageConverter.cs +++ b/src/BinaryKits.Zpl.Protocol/ImageConverters/ImageSharpImageConverter.cs @@ -17,7 +17,7 @@ public ImageResult ConvertImage(byte[] imageData) { var zplBuilder = new StringBuilder(); - using (Image image = Image.Load(imageData)) + using (Image image = Image.Load(imageData).CloneAs()) { var bytesPerRow = image.Width % 8 > 0 ? image.Width / 8 + 1 @@ -30,11 +30,9 @@ public ImageResult ConvertImage(byte[] imageData) for (var y = 0; y < image.Height; y++) { - var row = image.GetPixelRowSpan(y); - for (var x = 0; x < image.Width; x++) { - var pixel = row[x]; + var pixel = image[x, y]; var isBlackPixel = ((pixel.R + pixel.G + pixel.B) / 3) < 128; if (isBlackPixel) @@ -88,14 +86,13 @@ public byte[] ConvertImage(byte[] imageData, int bytesPerRow) { for (var y = 0; y < image.Height; y++) { - var row = image.GetPixelRowSpan(y); var bits = new BitArray(imageData.Skip(bytesPerRow * y).Take(bytesPerRow).ToArray()); for (var x = 0 ; x < image.Width; x++) { if (bits[x]) { - row[x].A = 255; + image[x, y] = new Rgba32(0, 0, 0, 255); } } } diff --git a/src/BinaryKits.Zpl.TestConsole/Program.cs b/src/BinaryKits.Zpl.TestConsole/Program.cs index 7c68faa2..e74a9429 100644 --- a/src/BinaryKits.Zpl.TestConsole/Program.cs +++ b/src/BinaryKits.Zpl.TestConsole/Program.cs @@ -84,10 +84,11 @@ static string RenderLabel3() { new ZplBarcode128("Barcode128", 10, 0), new ZplBarcode39("Barcode39", 10, 150), - new ZplBarcodeAnsiCodabar("123456", 'a', 'd', 10, 300, 100), - new ZplBarcodeEan13("123456789", 10, 450), - new ZplBarcodeInterleaved2of5("123456789", 10, 600), - new ZplQrCode("BinaryKits ZplUtility BinaryKits ZplUtility BinaryKits ZplUtility", 10, 800, magnificationFactor: 6) + new ZplBarcode93("Barcode93", 10, 300), + new ZplBarcodeAnsiCodabar("123456", 'a', 'd', 10, 450, 100), + new ZplBarcodeEan13("123456789", 10, 600), + new ZplBarcodeInterleaved2of5("123456789", 10, 750), + new ZplQrCode("BinaryKits ZplUtility BinaryKits ZplUtility BinaryKits ZplUtility", 10, 950, magnificationFactor: 6) }; var renderEngine = new ZplEngine(elements); @@ -192,7 +193,7 @@ static string RenderLabel6() new ZplTextBlock(text, 10, 120, 400, 100, font1, NewLineConversionMethod.ToSpace), new ZplTextBlock(text, 10, 240, 400, 100, font2, NewLineConversionMethod.ToEmpty), - + new ZplFieldBlock(text, 10, 360, 400, font1, 4) }; diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/BinaryKits.Zpl.Viewer.UnitTest.csproj b/src/BinaryKits.Zpl.Viewer.UnitTest/BinaryKits.Zpl.Viewer.UnitTest.csproj index bd475c60..4e30eba7 100644 --- a/src/BinaryKits.Zpl.Viewer.UnitTest/BinaryKits.Zpl.Viewer.UnitTest.csproj +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/BinaryKits.Zpl.Viewer.UnitTest.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -19,5 +19,9 @@ - + + + Always + + diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Common.cs b/src/BinaryKits.Zpl.Viewer.UnitTest/Common.cs new file mode 100644 index 00000000..2819a926 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/Common.cs @@ -0,0 +1,53 @@ +using System.IO; +using BinaryKits.Zpl.Viewer.ElementDrawers; + +namespace BinaryKits.Zpl.Viewer.UnitTest +{ + internal class Common + { + /// + /// Load zpl strings from files in the Data/Zpl directory + /// + /// name of of the file, .zpl2 extension optional + /// + public static string LoadZPL(string name) { + if (!name.Contains(".zpl2")) + { + name += ".zpl2"; ; + } + var path = Path.Combine("Data", "Zpl", name); + return System.IO.File.ReadAllText(path); + } + + /// + /// Generic printer to test zpl -> png output + /// + /// + /// PNG filename ex: "file.png" + /// + /// + /// + /// + public static void DefaultPrint( + string zpl, + string outputFilename, + double width = 101.6, + double height = 152.4, + int ppmm = 8, + DrawerOptions options = null + ) + { + IPrinterStorage printerStorage = new PrinterStorage(); + var drawer = new ZplElementDrawer(printerStorage, options); + + var analyzer = new ZplAnalyzer(printerStorage); + var analyzeInfo = analyzer.Analyze(zpl); + + foreach (var labelInfo in analyzeInfo.LabelInfos) + { + var imageData = drawer.Draw(labelInfo.ZplElements, width, height, ppmm); + File.WriteAllBytes(outputFilename, imageData); + } + } + } +} diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/CustomTest.cs b/src/BinaryKits.Zpl.Viewer.UnitTest/CustomTest.cs new file mode 100644 index 00000000..158d4166 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/CustomTest.cs @@ -0,0 +1,15 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BinaryKits.Zpl.Viewer.UnitTest +{ + [TestClass] + public class CustomTest + { + [TestMethod] + public void Custom() + { + string zplString = Common.LoadZPL("custom"); + Common.DefaultPrint(zplString, "custom.png"); + } + } +} diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Png/Ref/.gitkeep b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Png/Ref/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Png/Test/.gitkeep b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Png/Test/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/custom.zpl2 b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/custom.zpl2 new file mode 100644 index 00000000..e69de29b diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/font-assign.zpl2 b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/font-assign.zpl2 new file mode 100644 index 00000000..b36f491c --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/font-assign.zpl2 @@ -0,0 +1,6 @@ +^XA +^FO20, 20 +^A1N,40, 30 ^FD西瓜^FS +^FO20, 50 +^A0N,40, 30 ^FDABCDEFG^FS +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert1.zpl2 b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert1.zpl2 new file mode 100644 index 00000000..56b90ce5 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert1.zpl2 @@ -0,0 +1,14 @@ +^XA +^FO10,100 +^GB70,70,70,,3^FS +^FO110,100 +^GB70,70,70,,3^FS +^FO210,100 +^GB70,70,70,,3^FS +^FO310,100 +^GB70,70,70,,3^FS +^FO17,110 +^CF0,70,93 +^FR +^FDREVERSE^FS +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert2.zpl2 b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert2.zpl2 new file mode 100644 index 00000000..61236a71 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/invert2.zpl2 @@ -0,0 +1,46 @@ +^XA +^FR +^FO50,50 +^GB100,100,10,W,5^FS +^FR +^FO200,50 +^GB100,100,10,W,5^FS +^FO100,120 +^GB30,25,10,B,2^FS +^FO250,120 +^GB30,25,10,B,2^FS +^FO130,180 +^GB90,90,45,B,8^FS +^FR +^FO75,300 +^GB30,20,10,W,0^FS +^FR +^FO265,300 +^GB30,20,10,W,0^FS +^FR +^FO105,320 +^GB160,20,10,W,0^FS +^FR +^FO120,310 +^GB10,20,5,B,0^FS +^FR +^FO140,310 +^GB10,20,5,B,0^FS +^FR +^FO160,310 +^GB10,20,5,B,0^FS +^FR +^FO180,310 +^GB10,20,5,B,0^FS +^FR +^FO200,310 +^GB10,20,5,B,0^FS +^FR +^FO220,310 +^GB10,20,5,B,0^FS +^FR +^FO240,310 +^GB10,20,5,B,0^FS +^FO150,330 +^GB70,90,45,B,0^FS +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/merge.zpl2 b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/merge.zpl2 new file mode 100644 index 00000000..615476e4 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/Data/Zpl/merge.zpl2 @@ -0,0 +1,21 @@ +^XA +^DFETIQUE-1^FS +^PRC +^LH0,0^FS +^LL408 +^MD0 +^MNY +^LH0,0^FS +^FO120,141^A0N,27,23^CI13^FR^FN999^FS +^BY2,3.0^FO213,7^BCN,80,N,Y,N^FR^FN997^FS +^FO313,95^A0N,35,23^CI13^FR^FB105,2,0,L^FN997^FS +^FO40,141^A0N,27,33^CI13^FR^FDP/N :^FS +^XZ + +^XA +^XFETIQUE-1.ZPL +^FN999^FDC19755BA01:F9111^FS +^FN997^FD3758292^FS +^PQ1,0,1,N +^XZ +^FX \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/DrawerTest.cs b/src/BinaryKits.Zpl.Viewer.UnitTest/DrawerTest.cs index 26b52851..84fce354 100644 --- a/src/BinaryKits.Zpl.Viewer.UnitTest/DrawerTest.cs +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/DrawerTest.cs @@ -1,11 +1,7 @@ using BinaryKits.Zpl.Viewer.ElementDrawers; using Microsoft.VisualStudio.TestTools.UnitTesting; using SkiaSharp; -using System; -using System.Collections.Generic; -using System.IO; -using ZXing; -using ZXing.Datamatrix; + namespace BinaryKits.Zpl.Viewer.UnitTest { @@ -15,13 +11,7 @@ public class DrawerTest [TestMethod] public void FontAssignment() { - string zplString = @" -^XA -^FO20, 20 -^A1N,40, 30 ^FD西瓜^FS -^FO20, 50 -^A0N,40, 30 ^FDABCDEFG^FS -^XZ"; + string zplString = Common.LoadZPL("font-assign"); var drawOptions = new DrawerOptions() { @@ -40,56 +30,26 @@ public void FontAssignment() return SKTypeface.Default; } }; - IPrinterStorage printerStorage = new PrinterStorage(); - var drawer = new ZplElementDrawer(printerStorage, drawOptions); - - var analyzer = new ZplAnalyzer(printerStorage); - var analyzeInfo = analyzer.Analyze(zplString); - - foreach (var labelInfo in analyzeInfo.LabelInfos) - { - var imageData = drawer.Draw(labelInfo.ZplElements, 300, 300, 8); - File.WriteAllBytes("test.png", imageData); - } + Common.DefaultPrint(zplString, "font-assign.png", 300, 300, 8, drawOptions); } [TestMethod] public void FormatHandling() { - string zplString = @" -^XA -^DFETIQUE-1^FS -^PRC -^LH0,0^FS -^LL408 -^MD0 -^MNY -^LH0,0^FS -^FO120,141^A0N,27,23^CI13^FR^FN999^FS -^BY2,3.0^FO213,7^BCN,80,N,Y,N^FR^FN997^FS -^FO313,95^A0N,35,23^CI13^FR^FB105,2,0,L^FN997^FS -^FO40,141^A0N,27,33^CI13^FR^FDP/N :^FS -^XZ - - -^XA -^XFETIQUE-1.ZPL -^FN999^FDC19755BA01:F9111^FS -^FN997^FD3758292^FS -^PQ1,0,1,N -^XZ -^FX"; - IPrinterStorage printerStorage = new PrinterStorage(); - var drawer = new ZplElementDrawer(printerStorage); + string zplString = Common.LoadZPL("merge"); + Common.DefaultPrint(zplString, "merge.png", 100, 100, 8); + } - var analyzer = new ZplAnalyzer(printerStorage); - var analyzeInfo = analyzer.Analyze(zplString); + [TestMethod] + public void InvertColor() + { + // Example in ZPL manual + string test1 = Common.LoadZPL("invert1"); + // from https://github.com/BinaryKits/BinaryKits.Zpl/pull/64 + string test2 = Common.LoadZPL("invert2"); - foreach (var labelInfo in analyzeInfo.LabelInfos) - { - var imageData = drawer.Draw(labelInfo.ZplElements, 300, 300, 8); - File.WriteAllBytes("merge-test.png", imageData); - } + Common.DefaultPrint(test1, "inverted1.png", 100, 100, 8); + Common.DefaultPrint(test2, "inverted2.png"); } } -} +} \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.UnitTest/README.md b/src/BinaryKits.Zpl.Viewer.UnitTest/README.md new file mode 100644 index 00000000..bcd70be4 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.UnitTest/README.md @@ -0,0 +1,23 @@ +# Viewer Unit Test + +This project (should) supports the Viewer project in multiple ways. + +## Standard unit testing +Mostly testing the features of the virtual printer(s) and analyzers. + +## Detect changes and regressions in png outputs +Given a set of known good png output files, we should be able to detect when new output changes. +This would be done by loading 2 pngs into skia bitmaps and xor-ing them. We can then count the amount of black pixels and output a new png showing the difference. + +## Feature development +The `CustomTest.cs` exists for quick development. Load your zpl data in `Data/Zpl/custom.zpl2` and test. Changes to these files are not tracked under source control. Use `git update-index --no-skip-worktree custom.zpl2` and `git update-index --skip-worktree custom.zpl2` (see https://stackoverflow.com/a/39776107) to enable tracking changes to the files. + +## Benchmarks +Performance isn't stellar yet. We can use features in Visual Studio, but other tools might be more suitable like [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet). + +Note that a lot of time is spent inside 3rd party libraries like Skia, BarcodeLib and Zxing.net and Drawing. + +## Todo +- [ ] Linux compatibility +- [ ] Benchmarking +- [ ] Xor diffing pngs \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/BinaryKits.Zpl.Viewer.WebApi.csproj b/src/BinaryKits.Zpl.Viewer.WebApi/BinaryKits.Zpl.Viewer.WebApi.csproj index 5bee3af9..7305539f 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/BinaryKits.Zpl.Viewer.WebApi.csproj +++ b/src/BinaryKits.Zpl.Viewer.WebApi/BinaryKits.Zpl.Viewer.WebApi.csproj @@ -5,7 +5,7 @@ - + @@ -24,7 +24,7 @@ PreserveNewest - + PreserveNewest @@ -69,16 +69,16 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -126,6 +126,24 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Controllers/ViewerController.cs b/src/BinaryKits.Zpl.Viewer.WebApi/Controllers/ViewerController.cs index de979c27..f3842865 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/Controllers/ViewerController.cs +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Controllers/ViewerController.cs @@ -1,4 +1,5 @@ -using BinaryKits.Zpl.Viewer.WebApi.Models; +using BinaryKits.Zpl.Viewer.ElementDrawers; +using BinaryKits.Zpl.Viewer.WebApi.Models; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -34,25 +35,68 @@ public ActionResult Render(RenderRequestDto request) private ActionResult RenderZpl(RenderRequestDto request) { IPrinterStorage printerStorage = new PrinterStorage(); - var drawer = new ZplElementDrawer(printerStorage); + var drawerOptions = new DrawerOptions(); + drawerOptions.OpaqueBackground = true; //set white background for viewer requests + + //PDF mode (image mode is default) + if (request.Type == "PDF") + { + drawerOptions.PdfOutput = true; + } + + var drawer = new ZplElementDrawer(printerStorage, drawerOptions); var analyzer = new ZplAnalyzer(printerStorage); var analyzeInfo = analyzer.Analyze(request.ZplData); var labels = new List(); + var pdfs = new List(); foreach (var labelInfo in analyzeInfo.LabelInfos) { - var imageData = drawer.Draw(labelInfo.ZplElements, request.LabelWidth, request.LabelHeight, request.PrintDensityDpmm); - var label = new RenderLabelDto + if (request.Type == "image") + { + var imageData = drawer.Draw(labelInfo.ZplElements, request.LabelWidth, request.LabelHeight, request.PrintDensityDpmm); + var label = new RenderLabelDto + { + ImageBase64 = Convert.ToBase64String(imageData) + }; + labels.Add(label); + } + + if (request.Type == "PDF") + { + var pdfData = drawer.DrawPdf(labelInfo.ZplElements, request.LabelWidth, request.LabelHeight, request.PrintDensityDpmm); + var pdf = new RenderLabelDto + { + PdfBase64 = Convert.ToBase64String(pdfData) + }; + pdfs.Add(pdf); + } + + if (request.Type == "both") { - ImageBase64 = Convert.ToBase64String(imageData) - }; - labels.Add(label); + var bothData = drawer.DrawMulti(labelInfo.ZplElements, request.LabelWidth, request.LabelHeight, request.PrintDensityDpmm); + + var imageData = bothData[0]; + var label = new RenderLabelDto + { + ImageBase64 = Convert.ToBase64String(imageData) + }; + labels.Add(label); + + var pdfData = bothData[1]; + var pdf = new RenderLabelDto + { + PdfBase64 = Convert.ToBase64String(pdfData) + }; + pdfs.Add(pdf); + } } var response = new RenderResponseDto { Labels = labels.ToArray(), + Pdfs = pdfs.ToArray(), NonSupportedCommands = analyzeInfo.UnknownCommands }; diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example10-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example10-102x152.zpl2 new file mode 100644 index 00000000..763f658b --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example10-102x152.zpl2 @@ -0,0 +1,109 @@ +^XA +^CI28 +^FO560,30^GFA,924,924,28, +O01PFEK03JFC00KF01KF,O01QFEJ07JFC01JFE03KF,O03RFJ07JF803JFE07JFE, +O07RF8I0KF007JFC0KFC,O0SFC001KF007JF80KF8,O0SFE003JFE00KF01KF8, +N01SFE003JFC01KF03KF,N03TF007JF803JFE03JFE,N07TF00KF803JFC07JFC, +N07TF00KF007JFC0KFC,X0KF01JFE00KF81KF8,X07IFE03RF01KF, +X0JFE03QFE03KF,P01JFE01JFC07QFE07JFE,P03JFE03JF80RFC07JFC, +P03JFC03JF81RF80KFC,P07JF807JF01RF81KF8,P0KF00JFE03RF01KF, +P0KF00JFE07QFE03JFE,O01JFE,O03JFC,O07JFE, +NF87QF03JFE00KF01QF87KFENF0QFE03JFC01KF03QF8LFEN01QFC07JF803JFE07QF, +N01QF80KF803JFC07PFE, +MFC3QF01KF007JF80QFE3LFEMF87PFE01JFE00KF80QFC7LFEN0QFC03JFC00KF00QF8, +N0QF007JFC01JFE007PF,L021PFC007JF803JFC007PF, +LFE3PFI0KF007JFC001OFE1MFE7KFC3NFEI01JFE007JF8I01NFC3MFC^FS + + +Horizontal line + +^FO0000,0075^GB0799,0000,0002^FS Lijn 1 +^FO0000,0138^GB0799,0000,0002^FS Lijn 2 +^FO0000,0383^GB0799,0000,0002^FS Lijn 3 +^FO0000,0440^GB0799,0000,0002^FS Lijn 4 +^FO0000,0490^GB0799,0000,0002^FS Lijn 5 + +Vertical Line + +^FO0680,0075^GB0000,0065,0002^FS Lijn 1 +^FO0560,0385^GB0000,0057,0002^FS Lijn 2.0 +^FO0670,0385^GB0000,0057,0002^FS Lijn 2.1 +^FO0670,0431^GB0000,0059,0002^FS Lijn 3 + +^CW2,E:ARIBLK.FNT^FS +^CW4,E:BOOKOS.FNT^FS + +^FO0200,0030^A0N,55,35^CI28^FDDHL PARCEL CONNECT^FS +^FO015,0030^A0N,55,32^CI28^FR^FDCMR^FS +^FO0100,0030^A0N,18,20^CI28^FH^FDLabel^FS +^FO0100,0050^A0N,18,20^CI28^FH^FDversion 5.7^FS +^FO0035,0070^A0N,18,20^CI28^FD^FS + +TEXT FROM START +^A0N,22,22^FO35,85^CI28^FDFROM:^FS +^A0N,25,25^FO400,85^CI28^FDContact:^FS VAR GEADRESEERDE FROM +^A0N,25,25^FO400,108^CI28^FD^FS VAR GEADRESEERDE FROM +^A0N,20,22^FO100,85^CI28^FDSome company^FS VAR SENDERS NAME +^A0N,20,22^FO100,103^CI28^FDSome street 10^FS VAR SENDERS STREET AND HOUSENUMBER +^A0N,20,22^FO145,121^CI28^FD9999 AA Someplace^FS VAR SENDERS POSTCODE AND CITY +^A0N,20,22^FO100,121^CI28^FDNL-^FS VAR SENDERS COUNTRYCODE +TEXT FROM END + +^FO100,165^GB75,5,3^FS HAAK LINKSBOVEN +^FO100,165^GB5,75,3^FS HAAK LINKSBOVEN +^FO625,165^GB75,5,3^FS HAAK RECHTSBOVEN +^FO700,165^GB5,75,3^FS HAAK RECHTSBOVEN +^FO100,355^GB75,5,3^FS HAAK LINKSONDER +^FO100,285^GB5,75,3^FS HAAK LINKSONDER +^FO625,355^GB75,5,3^FS HAAK RECHTSONDER +^FO700,285^GB5,75,3^FS HAAK RECHTSONDER + +TEXT TO START +^A0N,27,30^FO165,205^CI28^FDTO:^FS +^A0N,27,30^FO210,205^CI28^FD^FS VAR DELIVERY NAAM/STREET/HOUSENUMBER +^A0N,27,30^FO210,230^CI28^FDMevr. Some Persone^FS VAR DELIVERY NAAM/STREET/HOUSENUMBER +^A0N,27,30^FO210,255^CI28^FDStreet 17 E^FS VAR DELIVERY NAAM/STREET/HOUSENUMBER +^A0N,27,30^FO210,280^CI28^FD09999 Someplace^FS VAR DELIVERY POSTCODE/CITY/COUNTRY +^A0N,27,30^FO210,305^CI28^FDGermany^FS VAR DELIVERY POSTCODE/CITY/COUNTRY +TEXT TO END + +^FO0688,0449^A0N,48,48^CI28^FD^FS + +^FO0000,0383^GB0300,0000,0057^FS Black box +^FO25,0430^A0N,62,62^CI28^FR^FD^FS + +^FO0568,0390^A0N,21,21^CI28^FDDAY:^FS VAR DAY +^FO0625,0395^A0N,50,50^CI28^FD^FS VAR CONTENT DAY + +^FO0688,0390^A0N,21,21^CI28^FDTIME:^FS VAR TIME +^FO0665,0408^A0N,40,40^CI28^FD^FS VAR CONTENT TIME + + +^FO0020,0450^A0N,23,25^CI28^FH^FDAccount No.:^FS VAR ACCOUNT NO +^FO0170,0450^A0N,23,25^CI28^FH^FD012346578^FS VAR CONTENT ACCOUNT NO + +^FO0020,470^A0N,23,25^CI28^FDShipment ref.: ^FS VAR SHIPMENT REF +^FO170,470^A0N,23,25^CI28^FD1235456879^FS VAR CONTENT SHIPMENT REF + +^FO0350,0450^A0N,23,25^CI28^FDPackage Weight:^FS VAR PACKAGE WEIGHT +^FO0520,0450^A0N,23,25^CI28^FH^FD2,00 KGM^FS VAR CONTENT PACKAGE WEIGHT +^FO0350,0470^A0N,23,25^CI28^FDPickup date: ^FS VAR PICKUP DATE +^FO0520,0470^A0N,23,25^CI28^FH^FD20249999^FS VAR CONTENT PICKUP DATE + +^FO0675,0447^A0N,23,25^CI28^FDPiece:^FS VAR PIECE +^FO0735,0450^A0N,45,45^CI28^FD1/1^FS VAR CONTENT PIECE + + +PDF BARCODE START +^FO0100,0520^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'COM+SOMEEMAIL@GMAIL.COM:EM'GID+0+1'MEA+WT++KGM:2'PCI+ZZ1+JVGL09999999999994760001'UNT+13+123546798'^FS +PDF BARCODE END + +^BY3^FO100,0720^BCN,200,N,N,N^FD2LDE0+>570000000^FS VAR ROUTING BARCODE +^A0N,20,22^FO287,925^CI28^FD2LDE0+70000000^FS VAR TEXT UNDER ROUTING BARCODE + +^BY3^FO100,0955^BCN,200,N,N,N^FDJVGL>509999999999994760001^FS VAR LICENCE PLATE +^A0N,20,22^FO287,1160^CI28^FDJVGL09999999999994760001^FS VAR TEXT UNDER LICENCE PLATE +^PQ1,0,0, + +^XZ diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example11-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example11-102x152.zpl2 new file mode 100644 index 00000000..50a04fee --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example11-102x152.zpl2 @@ -0,0 +1,102 @@ +^XA +^PMN +^MUD +^LH15,0 +^CI28 +^JMA +^FO10,946,^GB616,137,0^FS +^FO10,703,^GB616,244,0^FS +^FO24,712,^A0N,24^FR^FD^FS +^FO24,743,^A0N,43^FR^FDJS Corporation^FS +^FO24,790,^A0N,31^FR^FDSales Department^FS +^FO24,824,^A0N,31^FR^FDc/o John Smith^FS +^FO24,857,^A0N,43^FR^FDImaginary Drive 25^FS +^FO24,903,^A0N,31^FR^FDGB^FS +^FO256,716,^A0N,18^FR^FDCustomer No^FS +^FO379,716,^A0N,18^FR^FD^FS +^FO112,1052,^A0N,24^FR^FD^FS +^FO18,953,^A0N,24^FR^FDContact^FS +^FO18,978,^A0N,24^FR^FDPhone^FS +^FO18,1003,^A0N,24^FR^FDNote^FS +^FO112,953,^A0N,24^FR^FD^FS +^FO112,978,^A0N,24^FR^FD^FS +^FO112,1003,^A0N,24^FR^FD^FS +^FO112,1028,^A0N,24^FR^FD^FS +^FO10,444,^GB616,260,0^FS +^FO18,1028,^A0N,24^FR^FDNote^FS +^FO18,1052,^A0N,24^FR^FDRef.No^FS +^FO625,444,^GB162,639,0^FS +^FO24,453,^A0N,42^FR^FD^FS +^FO24,502,^A0N,24^FR^FD^FS +^FO24,527,^A0N,24^FR^FD^FS +^FO24,556,^A0N,24^FR^FD^FS +^FO24,580,^A0N,32^FR^FD^FS +^FO24,628,^A0N,24^FR^FD^FS +^FO24,653,^A0N,24^FR^FD^FS +^FO24,678,^A0N,24^FR^FD^FS +^FO100,903,^A0N,31^FR^FDMK7 8LE^FS +^FO285,903,^A0N,31^FR^FDJohn Doe^FS +^FO630,514,^A0R,18^FR^FD12345^FS +^FO630,607,^A0R,18^FR^FDNevermind^FS +^FO630,970,^A0R,50^FR^FD^FS +^FO754,451,^A0R,18^FR^FDShipper^FS +^FO727,451,^A0R,24^FR^FDSome Services GmbH^FS +^FO754,572,^A0R,18^FR^FDCust.ID^FS +^FO754,692,^A0R,18^FR^FD20000012^FS +^FO754,840,^A0R,18^FR^FDCon.ID^FS +^FO754,946,^A0R,18^FR^FD27600000aB^FS +^FO701,451,^A0R,18^FR^FD^FS +^FO677,451,^A0R,18^FR^FD^FS +^FO654,451,^A0R,18^FR^FDUTF8-Test-ÄöüÜÖÄâá-Straße 1-7^FS +^FO630,451,^A0R,18^FR^FDDE^FS +^FO570,12,^GB224,90,90^FS +^FO248,13,^GB71,89,71^FS +^FO4,5,^GB790,9,9^FS +^FO27,120,^A0N,62^FR^FD301^FS +^FO183,139,^A0N,43^FR^FDMK7 8LE^FS +^FO25,28,^A0N,74^FR^FDCVT^FS +^FO450,30,^A0N,74^FR^FDGB^FS +^FO7,102,^GB787,6,6^FS +^FO263,25,^A0N,80^FR^FD0^FS +^FO216,110,^A0N,18^FR^FDZipcode^FS +^FO384,110,^A0N,18^FR^FDYour GLS Track ID^FS +^FO387,147,^A0N,34^FR^FDABCDEF0^FS +^FO8,184,^GB787,6,6^FS +^FO8,395,^GB783,9,9^FS +^FO50,217,^BY4^BXN,4,200^FR^FDA 000062276000001227600000aBABCDEF0 0CVT0301 00100001001 ^FS +^FO595,214,^BY4^BXN,4,200^FR^FDA|Some Corporation|Imaginary Drive|John Doe|25||| ^FS +^FO8,190,^GB24,8,8^FS +^FO8,196,^GB8,16,8^FS +^FO8,372,^GB8,16,8^FS +^FO8,387,^GB24,8,8^FS +^FO229,387,^GB24,8,8^FS +^FO244,196,^GB8,16,8^FS +^FO228,190,^GB24,8,8^FS +^FO245,372,^GB8,16,8^FS +^FO580,28,^A0N,80^FR^FD0062^FS +^FO303,352,^A0N,37^FR^FD012345678900^FS +^FO720,416,^A0N,18^FR^FDF2.00.0^FS +^FO616,416,^A0N,18^FR^FD27082021^FS +^FO168,416,^A0N,18^FR^FD27.08.2021^FS +^FO32,408,^A0N,31^FR^FDDE 500^FS +^FO342,408,^A0N,37^FR^FD1.00^FS +^FO131,407,^A0N,31^FR^FD^FS +^FO273,416,^A0N,18^FR^FD10:33^FS +^FO481,416,^A0N,18^FR^FD001^FS +^FO525,416,^A0N,18^FR^FD001^FS +^FO510,416,^A0N,18^FR^FD/^FS +^FO572,416,^A0N,18^FR^FDRTG^FS +^FO268,221,^BY3,2.0,125,^B2N,125,N,N^FR^FD012345678900^FS +^FO568,135,^A0N,41^FR^FD^FS +^FO589,135,^A0N,41^FR^FD^FS +^FO654,135,^A0N,41^FR^FD^FS +^FO689,135,^A0N,41^FR^FD^FS +^FO752,135,^A0N,41^FR^FDS^FS +^FO772,135,^A0N,41^FR^FD^FS +^FO425,407,^A0N,37^FR^FDkg^FS +^FO18,1090,^A0N,20^FR^FDInformation about Data Protection in GLS Group can be found at^FS +^FO18,1115,^A0N,20^FR^FDgls-group.eu/dataprotection^FS +^FO18,1140,^A0N,20^FR^FD^FS +^FO18,1165,^A0N,20^FR^FD^FS +^PQ1 +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example12-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example12-102x152.zpl2 new file mode 100644 index 00000000..4e86bda9 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example12-102x152.zpl2 @@ -0,0 +1,110 @@ +^XA +^LRN +^MNY +^MFN,N +^LH10,12 +^MCY +^FX POI +^PW812 +^CI27 + +^FX Mode 2 Zebra example 1 +^FO20,435 +^BD2^FH^FD002840100450000_5B)>_1E01_1D961Z00136071_1DUPSN_1D123X56_1D028_1D_1D001/001_1D011_1DN_1D_1DNEW YORK_1DNY_1E_04^FS + +^FO620,1140 +^GFA,00969,00969,019,FFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000 +F0000000000001F8000000000000F000000000 +F0000000000001F8000000000000F000000000 +F0000000003F81F83FC000000000F000000000 +F0000000003F81F83FC000000000F000000000 +F000000000FFF9F9FFF000000000F000000000 +F000000000FFF9F9FFF000000000F000000000 +F000000000FFFFFFFFFC00000000F000000000 +F000000000FFFFFFFFFC00000000F000000000 +F000000000F07FFFF0FC00000000F000000000 +F000000000F07FFFF0FC00000000F000000000 +F000000000FC1FFFC3F000000000F000000000 +F000000000FC1FFFC3F000000000F000000000 +F000000000FFFFFFFFF000000000F000000000 +F000000000FFFFFFFFF000000000F000000000 +F0000000003FFFFFFFC000000000F000000000 +F0000000003FFFFFFFC000000000F000000000 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000 +F00000000001FFFFF00000000000F000000000 +F00000000001FFFFF00000000000F000000000 +F00000000003FFF9FC0000000000F000000000 +F00000000003FFF9FC0000000000F000000000 +F0000000003FE1F87FC000000000F000000000 +F0000000003FE1F87FC000000000F000000000 +F000000000FF81F83FF000000000F000000000 +F000000000FF81F83FF000000000F000000000 +F000000000FE01F803F000000000F000000000 +F000000000FE01F803F000000000F000000000 +F000000000F001F800F000000000F000000000 +F000000000F001F800F000000000F000000000 +F0000000000001F8000000000000F000000000 +F0000000000001F8000000000000F0FFDC1C00 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF0FFDC1C00 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF00C1E3C00 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF00C1E3C00 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF00C1A2C00 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF00C1B6C00 +FFFFFFFFFFFFFFFFFFFFFFFFFFFFF00C1B6C00 +0000000000000000000000000000000C1B6C00 +0000000000000000000000000000000C19CC00 +0000000000000000000000000000000C19CC00 +0000000000000000000000000000000C19CC00 +0000000000000000000000000000000C188C00 +00000000000000000000000000000000000000 +00000000000000000000000000000000000000 +00000000000000000000000000000000000000 +^DN +^FT55,587^A0N,175,220^FVH^FS +^FT15,23^A0N,20,24^FVBEE CHEW^FS +^FT15,42^A0N,20,24^FV1234567898 1^FS +^FT15,61^A0N,20,24^FVABC ASSOCIATES^FS +^FT15,81^A0N,20,24^FV34 QUEEN ST^FS +^FT15,100^A0N,20,24^FVTIMONIUM MD 21093^FS +^FT60,181^A0N,26,30^FVSHIP TO ATTN NAME^FS +^FT60,208^A0N,26,30^FV1234567890^FS +^FT60,236^A0N,26,30^FVSUNGJIN^FS +^FT60,263^A0N,26,30^FV34 QUEEN ST^FS +^FT60,307^A0N,45,44^FVTIMONIUM MD 21093^FS +^FT380,30^A0N,30,34^FV102 LBS ^FS +^FT673,34^A0N,28,32^FV 1 OF 2^FS +^FT500,69^A0N,22,26^FVDWT: 7,5,2^FS +^FT500,91^A0N,22,26^FVAH ^FS +^FT620,736^A0N,100,76^FV ^FS +^FO677,640^GB123,123,122^FS + +^FT300,618^BY3^BCN,103,N,N,,A^FV42021093^FS + +^FT290,493^A0N,80,70^FVMD 211 9-65^FS +^FT10,704^A0N,56,58^FVUPS GROUND^FS +^FT10,737^A0N,26,30^FVTRACKING #: 1Z RW0 175 03 9990 5329^FS +^FO0,762^GB800,4,4^FS + +^FT790,1039^A0N,22,26^FV ^FS +^FT10,1035^A0N,22,26^FVBILLING: ^FS +^FT126,1035^A0N,22,26^FVP/P ^FS +^FT10,1059^A0N,22,26^FVDESC: Description^FS +^FT15,153^A0N,28,32^FVSHIP TO: ^FS +^FO0,637^GB798,14,14^FS + +^FO0,997^GB800,14,14^FS + +^FO0,416^GB800,4,4^FS + +^FO240,416^GB3,221,3^FS + +^FT190,1188^A0N,14,20^FVXOL 18.09.09 NV45 06.0A 10/2018^FS +^FT105,982^BY3^BCN,202,N,N,,A^FV1ZRW01750399905329^FS + +^FT273,896^A0N,95,74^FVSAMPLE^FS +^XZ diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example3-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example3-54x86.zpl2 similarity index 100% rename from src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example3-102x152.zpl2 rename to src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example3-54x86.zpl2 diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example5-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example5-75x202.zpl2 similarity index 100% rename from src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example5-102x152.zpl2 rename to src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example5-75x202.zpl2 diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example6-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example6-75x254.zpl2 similarity index 100% rename from src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example6-102x152.zpl2 rename to src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example6-75x254.zpl2 diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example7-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example7-178x152.zpl2 similarity index 100% rename from src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example7-102x152.zpl2 rename to src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example7-178x152.zpl2 diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example8-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example8-64x152.zpl2 similarity index 100% rename from src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example8-102x152.zpl2 rename to src/BinaryKits.Zpl.Viewer.WebApi/Labels/Example/Example8-64x152.zpl2 diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode128-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode128-102x152.zpl2 index dca1ed02..e32c9662 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode128-102x152.zpl2 +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode128-102x152.zpl2 @@ -5,33 +5,33 @@ ^FO10,10 ^BY3 ^BCN,100,Y -^FD123ABC^FS +^FDABC12345^FS ^FO10,160 ^BY4 ^BCN,100,Y -^FD123ABC^FS +^FDABC12345^FS ^FO10,320 ^BY5 ^BCN,100,Y -^FD123ABC^FS +^FDABC12345^FS ^FX Automatic Mode ^FO10,500 ^BY3 ^BCN,100,Y,,,A -^FD123ABC^FS +^FDABC12345^FS ^FO10,650 ^BY4 ^BCN,100,Y,,,A -^FD123ABC^FS +^FDABC12345^FS ^FO10,810 ^BY5 ^BCN,100,Y,,,A -^FD123ABC^FS +^FDABC12345^FS ^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode93-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode93-102x152.zpl2 new file mode 100644 index 00000000..c962c23a --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/Barcode93-102x152.zpl2 @@ -0,0 +1,18 @@ +^XA + +^FO10,10 +^BY3,2 +^BAN,100,Y,N +^FD123ABC^FS + +^FO10,160 +^BY4,2 +^BAN,100,Y,N +^FD123ABC^FS + +^FO10,320 +^BY5,2 +^BAN,100,Y,N +^FD123ABC^FS + +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodeEAN13-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodeEAN13-102x152.zpl2 index e63e695a..8e8b7423 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodeEAN13-102x152.zpl2 +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodeEAN13-102x152.zpl2 @@ -1,18 +1,28 @@ ^XA -^FO10,10 -^BY3 +^FO60,10 +^BY1 +^BEN,50,Y +^FD123456789012^FS + +^FO60,90 +^BY2 ^BEN,100,Y ^FD123456789012^FS -^FO10,160 +^FO60,230 +^BY3 +^BEN,150,Y +^FD123456789012^FS + +^FO60,430 ^BY4 -^BEN,100,Y +^BEN,200,Y ^FD123456789012^FS -^FO10,320 +^FO60,700 ^BY5 -^BEN,100,Y +^BEN,250,Y ^FD123456789012^FS ^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodePDF417-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodePDF417-102x152.zpl2 new file mode 100644 index 00000000..3401bb89 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/BarcodePDF417-102x152.zpl2 @@ -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'COM+SOMEEMAIL@GMAIL.COM: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'COM+SOMEEMAIL@GMAIL.COM: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 \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/FieldReversePrint3-54x86.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/FieldReversePrint3-54x86.zpl2 new file mode 100644 index 00000000..9bbe758c --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/FieldReversePrint3-54x86.zpl2 @@ -0,0 +1,107 @@ +^XA + +^FX Happy face test +^FR +^FO50,50 +^GB100,100,10,W,5^FS + +^FR +^FO200,50 +^GB100,100,10,W,5^FS + +^FO100,120 +^GB30,25,10,B,2^FS + +^FO250,120 +^GB30,25,10,B,2^FS + +^FO130,180 +^GB90,90,45,B,8^FS + +^FR +^FO75,300 +^GB30,20,10,W,0^FS + +^FR +^FO265,300 +^GB30,20,10,W,0^FS + +^FR +^FO105,320 +^GB160,20,10,W,0^FS + +^FR +^FO120,310 +^GB10,20,5,B,0^FS + +^FR +^FO140,310 +^GB10,20,5,B,0^FS + +^FR +^FO160,310 +^GB10,20,5,B,0^FS + +^FR +^FO180,310 +^GB10,20,5,B,0^FS + +^FR +^FO200,310 +^GB10,20,5,B,0^FS + +^FR +^FO220,310 +^GB10,20,5,B,0^FS + +^FR +^FO240,310 +^GB10,20,5,B,0^FS + +^FO150,330 +^GB70,90,45,B,0^FS + +^FX Reverse white over white test +^FO10,510 +^GB20,20,10,W,0^FS + +^FR +^FO20,510 +^GB20,20,10,W,0^FS + +^FR +^FO30,510 +^GB20,20,10,W,0^FS + +^FX Reverse black over black test +^FO10,540 +^GB20,20,10,B,0^FS + +^FR +^FO20,540 +^GB20,20,10,B,0^FS + +^FR +^FO30,540 +^GB20,20,10,B,0^FS + +^FX Reverse ZPL test +^FO10,600 +^GB70,70,70,,3^FS + +^FO110,600 +^GB70,70,70,,3^FS + +^FO210,600 +^GB70,70,70,,3^FS + +^FO310,600 +^GB70,70,70,,3^FS + +^FO17,610 +^CF0,70,93 +^FR +^FDREVERSE^FS + + +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/GS1-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/GS1-102x152.zpl2 new file mode 100644 index 00000000..0e3fc7f1 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/GS1-102x152.zpl2 @@ -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 \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/MaxiCode-102x152.zpl2 b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/MaxiCode-102x152.zpl2 new file mode 100644 index 00000000..8786a6cf --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Labels/Test/MaxiCode-102x152.zpl2 @@ -0,0 +1,27 @@ +^XA + +^FX Mode 2 Zebra example 1 +^FO10,10 +^BD2^FH^FD002840100450000_5B)>_1E01_1D961Z00136071_1DUPSN_1D123X56_1D028_1D_1D001/001_1D011_1DN_1D_1DNEW YORK_1DNY_1E_04^FS + +^FX Mode 3 Zebra example 2 +^FO250,250 +^BD3^FH^FD066826RS19 _5B)>_1E01_1D961Z00136111_1DUPSN_1D123X56_1D057_1D_1D001/001_1D011_1DN_1D_1DWEST SWINDON_1D_1E_04^FS + +^FX Mode 3 Zebra example 3 +^FO500,500 +^BD3^FH^FD00163000901 [)>_1E01_1D961Z00004951_1DUPSN_1D06X610_1D159_1D1234567_1D1/1_1D20_1DY_1D634 ALPHA DR_1DSAN JUAN_1DPR_1E_04^FS + +^FX Mode 4 +^FO10,500 +^BD4^FD123456789^FS + +^FX Mode 5 +^FO10,700 +^BD5^FD123456789^FS + +^FX Mode 6 +^FO10,900 +^BD6^FD123456789^FS + +^XZ \ No newline at end of file diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderLabelDto.cs b/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderLabelDto.cs index ab842447..70373aff 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderLabelDto.cs +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderLabelDto.cs @@ -3,5 +3,6 @@ public class RenderLabelDto { public string ImageBase64 { get; set; } + public string PdfBase64 { get; set; } } } diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderRequestDto.cs b/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderRequestDto.cs index 217f6a1e..2c0f9228 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderRequestDto.cs +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderRequestDto.cs @@ -18,5 +18,9 @@ public class RenderRequestDto /// Dots per Millimeter /// public int PrintDensityDpmm { get; set; } = 8; + /// + /// File type + /// + public string Type { get; set; } = "image"; } } diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderResponseDto.cs b/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderResponseDto.cs index 77cbeaae..b3f32f00 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderResponseDto.cs +++ b/src/BinaryKits.Zpl.Viewer.WebApi/Models/RenderResponseDto.cs @@ -4,5 +4,6 @@ public class RenderResponseDto { public string[] NonSupportedCommands { get; set; } public RenderLabelDto[] Labels { get; set; } + public RenderLabelDto[] Pdfs { get; set; } } } diff --git a/src/BinaryKits.Zpl.Viewer.WebApi/wwwroot/index.html b/src/BinaryKits.Zpl.Viewer.WebApi/wwwroot/index.html index 0c643c7a..f7d9571e 100644 --- a/src/BinaryKits.Zpl.Viewer.WebApi/wwwroot/index.html +++ b/src/BinaryKits.Zpl.Viewer.WebApi/wwwroot/index.html @@ -3,26 +3,55 @@ BinaryKits.Zpl.Viewer.WebApi - - - + + + +
-
-
- Loading... + +
+
+
+
+ Loading... +
+
+
+
+ {{ label.name }} {{ label.format }} +
+
-
-
-
- {{ label.name }} {{ label.format }} +
+
+
+ Loading... +
+
+
+
+ {{ label.name }} {{ label.format }} +
+
@@ -38,7 +67,12 @@ mm ({{labelHeightInch}} inches) dpmm ({{dpi}} dpi)
- +
+ + + + +

@@ -96,7 +130,9 @@

Label rendering error

loadingTestData: false, loadingPreview: false, zplData: null, - labels: null, + testLabels: null, + exampleLabels: null, + pdfs: null, renderResponse: null, renderError: null, printDensityDpmm: 8, @@ -144,12 +180,28 @@

Label rendering error

try { this.loadingTestData = true const response = await axios.get('api/v1/label') - this.labels = response.data.items + + let labels = response.data.items; + labels.sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base', numeric: true })); + + this.testLabels = labels.filter(function(label){ + return label.category === 'Test'; + }); + + this.exampleLabels = labels.filter(function(label){ + return label.category === 'Example'; + }); } finally { this.loadingTestData = false } }, - async changeZplData(zplData) { + async changeZplData(zplData, formatData = '') { + let formatParts = formatData.split("x"); + if (formatParts.length === 2) { + this.labelWidth = formatParts[0]; + this.labelHeight = formatParts[1]; + } + this.zplData = zplData await this.render() }, @@ -176,6 +228,65 @@

Label rendering error

this.loadingPreview = false } }, + async downloadPDF() { + this.renderError = null + try { + const payload = { + zplData: this.zplData, + printDensityDpmm: this.printDensityDpmm, + labelWidth: this.labelWidth, + labelHeight: this.labelHeight, + type: 'PDF' + } + const response = await axios.post('api/v1/viewer', payload) + + response.data.pdfs.forEach(function (labelInfo, index) { + var element = document.createElement('a'); + element.setAttribute('href', 'data:application/pdf;base64,' + labelInfo.pdfBase64); + element.setAttribute('download', 'label-'+ index +'.pdf'); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + }); + } catch(error) { + this.renderError = error.response.data + console.error(this.renderError) + } + }, + async downloadZpl() { + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(this.zplData)); + element.setAttribute('download', 'label.zpl'); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + }, + async downloadPng() { + try { + this.renderResponse.labels.forEach(function (labelInfo, index) { + var element = document.createElement('a'); + element.setAttribute('href', 'data:image/png;base64,' + labelInfo.imageBase64); + element.setAttribute('download', 'label-'+ index +'.png'); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); + }); + } catch(error) { + console.error(error); + } + }, async renderOverlay() { if(this.showLabelaryOverlay) { try { @@ -209,4 +320,4 @@

Label rendering error

Vue.createApp(App).mount('#app'); - \ No newline at end of file + diff --git a/src/BinaryKits.Zpl.Viewer/BinaryKits.Zpl.Viewer.csproj b/src/BinaryKits.Zpl.Viewer/BinaryKits.Zpl.Viewer.csproj index a4fd9e5f..b7d33fb9 100644 --- a/src/BinaryKits.Zpl.Viewer/BinaryKits.Zpl.Viewer.csproj +++ b/src/BinaryKits.Zpl.Viewer/BinaryKits.Zpl.Viewer.csproj @@ -1,10 +1,10 @@ - + net472;netstandard2.0;net6.0 This package provides a rendering logic for ZPL data, as an alternative to labelary.com. Binary Kits Pte. Ltd. - 1.1.5 + 1.2.0 Binary Kits Pte. Ltd. Zebra ZPL ZPL2 ZPLEmulator ZPLVirtualPrinter ZPLViewer ZPLParser @@ -22,15 +22,14 @@ - - - + + - - + + diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/Code93BarcodeZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/Code93BarcodeZplCommandAnalyzer.cs new file mode 100644 index 00000000..0e657a63 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/Code93BarcodeZplCommandAnalyzer.cs @@ -0,0 +1,55 @@ +using BinaryKits.Zpl.Label.Elements; +using BinaryKits.Zpl.Viewer.Models; + +namespace BinaryKits.Zpl.Viewer.CommandAnalyzers +{ + public class Code93BarcodeZplCommandAnalyzer : ZplCommandAnalyzerBase + { + public Code93BarcodeZplCommandAnalyzer(VirtualPrinter virtualPrinter) : base("^BA", virtualPrinter) { } + + /// + public override ZplElementBase Analyze(string zplCommand) + { + var zplDataParts = this.SplitCommand(zplCommand); + + int tmpint; + bool printCheckDigit = false; + int height = this.VirtualPrinter.BarcodeInfo.Height; + bool printInterpretationLine = true; + bool printInterpretationLineAboveCode = false; + + var fieldOrientation = this.ConvertFieldOrientation(zplDataParts[0]); + if (zplDataParts.Length > 1 && int.TryParse(zplDataParts[1], out tmpint)) + { + height = tmpint; + } + + if (zplDataParts.Length > 2) + { + printInterpretationLine = this.ConvertBoolean(zplDataParts[2], "Y"); + } + + if (zplDataParts.Length > 3) + { + printInterpretationLineAboveCode = this.ConvertBoolean(zplDataParts[3]); + } + + if (zplDataParts.Length > 4) + { + printCheckDigit = this.ConvertBoolean(zplDataParts[4]); + } + + //The field data are processing in the FieldDataZplCommandAnalyzer + this.VirtualPrinter.SetNextElementFieldData(new Code93BarcodeFieldData + { + FieldOrientation = fieldOrientation, + Height = height, + PrintInterpretationLine = printInterpretationLine, + PrintInterpretationLineAboveCode = printInterpretationLineAboveCode, + PrintCheckDigit = printCheckDigit + }); + + return null; + } + } +} diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldDataZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldDataZplCommandAnalyzer.cs index 84a3faf8..3f0b9f68 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldDataZplCommandAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldDataZplCommandAnalyzer.cs @@ -16,7 +16,7 @@ public class FieldDataZplCommandAnalyzer : ZplCommandAnalyzerBase private static readonly Regex qrCodeFieldDataMixedRegex = new Regex(@"^D\d{4}[0-9A-F-a-f]{2},(?[HQML])(?[AM]),(?.+)$", RegexOptions.Compiled); private static readonly Regex qrCodeFieldDataModeRegex = new Regex(@"^(?:[ANK]|(?:B(?\d{4})))(?.+)$", RegexOptions.Compiled); - public FieldDataZplCommandAnalyzer(VirtualPrinter virtualPrinter) : base("^FD", virtualPrinter) { } + public FieldDataZplCommandAnalyzer(VirtualPrinter virtualPrinter, string prefix = "^FD") : base(prefix, virtualPrinter) { } /// public override ZplElementBase Analyze(string zplCommand) @@ -47,11 +47,16 @@ public override ZplElementBase Analyze(string zplCommand) { int moduleWidth = this.VirtualPrinter.BarcodeInfo.ModuleWidth; double wideBarToNarrowBarWidthRatio = this.VirtualPrinter.BarcodeInfo.WideBarToNarrowBarWidthRatio; + bool useHexadecimalIndicator = this.VirtualPrinter.NextElementFieldUseHexadecimalIndicator; if (this.VirtualPrinter.NextElementFieldData is Code39BarcodeFieldData code39) { return new ZplBarcode39(text, x, y, code39.Height, moduleWidth, wideBarToNarrowBarWidthRatio, code39.FieldOrientation, code39.PrintInterpretationLine, code39.PrintInterpretationLineAboveCode, code39.Mod43CheckDigit, bottomToTop: bottomToTop); } + if (this.VirtualPrinter.NextElementFieldData is Code93BarcodeFieldData code93) + { + return new ZplBarcode93(text, x, y, code93.Height, moduleWidth, wideBarToNarrowBarWidthRatio, code93.FieldOrientation, code93.PrintInterpretationLine, code93.PrintInterpretationLineAboveCode, code93.PrintCheckDigit, bottomToTop: bottomToTop); + } if (this.VirtualPrinter.NextElementFieldData is Code128BarcodeFieldData code128) { return new ZplBarcode128(text, x, y, code128.Height, moduleWidth, wideBarToNarrowBarWidthRatio, code128.FieldOrientation, code128.PrintInterpretationLine, code128.PrintInterpretationLineAboveCode, bottomToTop, code128.Mode); @@ -69,6 +74,10 @@ public override ZplElementBase Analyze(string zplCommand) { return new ZplBarcodeInterleaved2of5(text, x, y, interleaved2of5.Height, moduleWidth, wideBarToNarrowBarWidthRatio, interleaved2of5.FieldOrientation, interleaved2of5.PrintInterpretationLine, interleaved2of5.PrintInterpretationLineAboveCode, bottomToTop: bottomToTop); } + if (this.VirtualPrinter.NextElementFieldData is MaxiCodeBarcodeFieldData maxiCode) + { + return new ZplMaxiCode(text, x, y, maxiCode.Mode, maxiCode.Position, maxiCode.Total, useHexadecimalIndicator, bottomToTop); + } if (this.VirtualPrinter.NextElementFieldData is QrCodeBarcodeFieldData qrCode) { (ErrorCorrectionLevel errorCorrection, string parsedText) = ParseQrCodeFieldData(qrCode, text); @@ -78,7 +87,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); } } @@ -98,7 +107,7 @@ public override ZplElementBase Analyze(string zplCommand) int lineSpace = this.VirtualPrinter.NextElementFieldBlock.AddOrDeleteSpaceBetweenLines; int hangingIndent = this.VirtualPrinter.NextElementFieldBlock.HangingIndentOfTheSecondAndRemainingLines; - return new ZplFieldBlock(text, x, y, width, font, maxLineCount, lineSpace, textJustification, hangingIndent, reversePrint : reversePrint, bottomToTop: bottomToTop); + return new ZplFieldBlock(text, x, y, width, font, maxLineCount, lineSpace, textJustification, hangingIndent, reversePrint: reversePrint, bottomToTop: bottomToTop); } return new ZplTextField(text, x, y, font, reversePrint: reversePrint, bottomToTop: bottomToTop); diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldHexadecimalZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldHexadecimalZplCommandAnalyzer.cs index fc05a5ca..0565578c 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldHexadecimalZplCommandAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldHexadecimalZplCommandAnalyzer.cs @@ -10,6 +10,7 @@ public FieldHexadecimalZplCommandAnalyzer(VirtualPrinter virtualPrinter) : base( /// public override ZplElementBase Analyze(string zplCommand) { + this.VirtualPrinter.SetNextElementFieldUseHexadecimalIndicator(); var zplDataParts = this.SplitCommand(zplCommand); char Indicator = '_'; diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldSeparatorZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldSeparatorZplCommandAnalyzer.cs index 6e344a85..16f56867 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldSeparatorZplCommandAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldSeparatorZplCommandAnalyzer.cs @@ -30,6 +30,7 @@ public override ZplElementBase Analyze(string zplCommand) this.VirtualPrinter.ClearNextElementFieldBlock(); this.VirtualPrinter.ClearNextElementFieldData(); this.VirtualPrinter.ClearNextElementFieldReverse(); + this.VirtualPrinter.ClearNextElementFieldUseHexadecimalIndicator(); this.VirtualPrinter.ClearNextFont(); this.VirtualPrinter.ClearComments(); diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldVarialbleZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldVarialbleZplCommandAnalyzer.cs new file mode 100644 index 00000000..581a6509 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldVarialbleZplCommandAnalyzer.cs @@ -0,0 +1,14 @@ +namespace BinaryKits.Zpl.Viewer.CommandAnalyzers +{ + // todo: fix virtual printer, must enable the MC command + // todo: factor out common parts from FieldDataZplCommandAnalyzer so both can inherit + // This is currently just a hack to be able to visualize single ups zpl. + // The FV command is normally used with the MC command when printing multiple labels of a same pattern. + // The MC command allows us to "save" the first label as a template + // using FDs as static elements of template and FVs as variable parts. + // Subsequent labels only require FV to draw the variable parts. + public class FieldVariableZplCommandAnalyzer : FieldDataZplCommandAnalyzer + { + public FieldVariableZplCommandAnalyzer(VirtualPrinter virtualPrinter): base(virtualPrinter, "^FV") { } + } +} diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicCircleZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicCircleZplCommandAnalyzer.cs index b2f99cc6..4ccdf2a1 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicCircleZplCommandAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/GraphicCircleZplCommandAnalyzer.cs @@ -44,7 +44,9 @@ public override ZplElementBase Analyze(string zplCommand) lineColor = lineColorTemp == "W" ? LineColor.White : LineColor.Black; } - return new ZplGraphicCircle(x, y, circleDiameter, borderThickness, lineColor, bottomToTop); + bool reversePrint = this.VirtualPrinter.NextElementFieldReverse || this.VirtualPrinter.LabelReverse; + + return new ZplGraphicCircle(x, y, circleDiameter, borderThickness, lineColor, reversePrint, bottomToTop); } } } diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/MaxiCodeBarcodeZplCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/MaxiCodeBarcodeZplCommandAnalyzer.cs new file mode 100644 index 00000000..9054094a --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/MaxiCodeBarcodeZplCommandAnalyzer.cs @@ -0,0 +1,43 @@ +using BinaryKits.Zpl.Label.Elements; +using BinaryKits.Zpl.Viewer.Models; +using System; + +namespace BinaryKits.Zpl.Viewer.CommandAnalyzers +{ + public class MaxiCodeBarcodeZplCommandAnalyzer : ZplCommandAnalyzerBase + { + public MaxiCodeBarcodeZplCommandAnalyzer(VirtualPrinter virtualPrinter) : base("^BD", virtualPrinter) { } + + /// + public override ZplElementBase Analyze(string zplCommand) + { + var zplDataParts = this.SplitCommand(zplCommand); + + int mode = 2; + int position = 1; + int total = 1; + + if (zplDataParts[0] != "") + { + mode = Int32.Parse(zplDataParts[0]); + } + + int tmpint; + if (zplDataParts.Length > 1 && int.TryParse(zplDataParts[1], out tmpint)) + { + position = tmpint; + } + + if (zplDataParts.Length > 2 && int.TryParse(zplDataParts[2], out tmpint)) + { + total = tmpint; + } + + this.VirtualPrinter.SetNextElementFieldData(new MaxiCodeBarcodeFieldData { + Mode = mode, Position = position, Total = total, + }); + + return null; + } + } +} diff --git a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/PDF417BarcodeCommandAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/PDF417BarcodeCommandAnalyzer.cs index 28cfd388..6d696c90 100644 --- a/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/PDF417BarcodeCommandAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/CommandAnalyzers/PDF417BarcodeCommandAnalyzer.cs @@ -2,6 +2,7 @@ using BinaryKits.Zpl.Label.Elements; using BinaryKits.Zpl.Viewer.Models; using System; +using System.Globalization; namespace BinaryKits.Zpl.Viewer.CommandAnalyzers { @@ -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)) diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode128ElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode128ElementDrawer.cs index 5bcd5e0c..79f3b330 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode128ElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode128ElementDrawer.cs @@ -1,119 +1,73 @@ -using BarcodeLib; using BinaryKits.Zpl.Label.Elements; -using BinaryKits.Zpl.Viewer.Helpers; +using BinaryKits.Zpl.Viewer.Symologies; using SkiaSharp; using System; -using System.Collections.Generic; -using System.Drawing; -using System.Text.RegularExpressions; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Code 128 Barcode elements + /// public class Barcode128ElementDrawer : BarcodeDrawerBase { - - /// - /// Start sequence lookups. - /// - /// - private static readonly Dictionary startCodeMap = new Dictionary() - { - { ">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); - - // As defined in BarcodeLib.Symbologies.Code128 - private static readonly string FNC1 = Convert.ToChar(200).ToString(); - /// public override bool CanDraw(ZplElementBase element) { return element is ZplBarcode128; } - /// - public override void Draw(ZplElementBase element) - { - Draw(element, new DrawerOptions()); - } - /// 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, ""); - string interpretation = content; + string content = barcode.Content; + Code128CodeSet codeSet = Code128CodeSet.Code128B; + bool gs1 = false; 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 + codeSet = Code128CodeSet.Code128B; } else if (barcode.Mode == "A") { - barcodeType = TYPE.CODE128; // dynamic + codeSet = Code128CodeSet.Code128; } else if (barcode.Mode == "D") { - barcodeType = TYPE.CODE128C; - content = content.Replace(">8", FNC1); - interpretation = interpretation.Replace(">8", ""); - if (!content.StartsWith(FNC1)) - { - content = FNC1 + content; - } + codeSet = Code128CodeSet.Code128C; + gs1 = true; } else if (barcode.Mode == "U") { - barcodeType = TYPE.CODE128C; + codeSet = Code128CodeSet.Code128C; + gs1 = true; content = content.PadLeft(19, '0').Substring(0, 19); int checksum = 0; for (int i = 0; i < 19; i++) { checksum += (content[i] - 48) * (i % 2 * 2 + 7); } - interpretation = string.Format("{0}{1}", interpretation, checksum % 10); - content = string.Format("{0}{1}{2}", FNC1, content, checksum % 10); + content = $">8{content}{checksum % 10}"; } float x = barcode.PositionX; float y = barcode.PositionY; - float labelFontSize = Math.Min(barcode.ModuleWidth * 7.2f, 72f); - var labelTypeFace = options.FontLoader("A"); - var labelFont = new SKFont(labelTypeFace, labelFontSize).ToSystemDrawingFont(); - int labelHeight = barcode.PrintInterpretationLine ? labelFont.Height : 0; - int labelHeightOffset = barcode.PrintInterpretationLineAboveCode ? labelHeight : 0; + var (data, interpretation) = ZplCode128Symbology.Encode(content, codeSet, gs1); + using var resizedImage = this.BoolArrayToSKBitmap(data.ToArray(), barcode.Height, barcode.ModuleWidth); + var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); + this.DrawBarcode(png, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation); - var barcodeElement = new Barcode + if (barcode.PrintInterpretationLine) { - BarWidth = barcode.ModuleWidth, - BackColor = Color.Transparent, - Height = barcode.Height + labelHeight, - IncludeLabel = barcode.PrintInterpretationLine, - LabelPosition = barcode.PrintInterpretationLineAboveCode ? LabelPositions.TOPCENTER : LabelPositions.BOTTOMCENTER, - LabelFont = labelFont, - AlternateLabel = interpretation - }; - - using var image = barcodeElement.Encode(barcodeType, content); - this.DrawBarcode(this.GetImageData(image), barcode.Height, image.Width, barcode.FieldOrigin != null, x, y, labelHeightOffset, barcode.FieldOrientation); + // TODO: use font 0, auto scale for Mode D + float labelFontSize = Math.Min(barcode.ModuleWidth * 10f, 100f); + var labelTypeFace = options.FontLoader("A"); + var labelFont = new SKFont(labelTypeFace, labelFontSize); + this.DrawInterpretationLine(interpretation, labelFont, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation, barcode.PrintInterpretationLineAboveCode, options); + } } } + } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode39ElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode39ElementDrawer.cs index 7936ee16..dab0a022 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode39ElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode39ElementDrawer.cs @@ -1,12 +1,13 @@ -using BarcodeLib; using BinaryKits.Zpl.Label.Elements; -using BinaryKits.Zpl.Viewer.Helpers; using SkiaSharp; using System; -using System.Drawing; +using ZXing.OneD; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Code 39 Barcode elements + /// public class Barcode39ElementDrawer : BarcodeDrawerBase { /// @@ -15,12 +16,6 @@ public override bool CanDraw(ZplElementBase element) return element is ZplBarcode39; } - /// - public override void Draw(ZplElementBase element) - { - Draw(element, new DrawerOptions()); - } - /// public override void Draw(ZplElementBase element, DrawerOptions options) { @@ -29,28 +24,25 @@ public override void Draw(ZplElementBase element, DrawerOptions options) float x = barcode.PositionX; float y = barcode.PositionY; - var content = barcode.Content; - var interpretation = string.Format("*{0}*", content.Trim('*')); + var content = barcode.Content.Trim('*'); + var interpretation = string.Format("*{0}*", content); - float labelFontSize = Math.Min(barcode.ModuleWidth * 7.2f, 72f); - var labelTypeFace = options.FontLoader("A"); - var labelFont = new SKFont(labelTypeFace, labelFontSize).ToSystemDrawingFont(); - int labelHeight = barcode.PrintInterpretationLine ? labelFont.Height : 0; - int labelHeightOffset = barcode.PrintInterpretationLineAboveCode ? labelHeight : 0; + var writer = new Code39Writer(); + var result = writer.encode(content); + int narrow = barcode.ModuleWidth; + int wide = (int)Math.Floor(barcode.WideBarToNarrowBarWidthRatio * narrow); + result = this.AdjustWidths(result, wide, narrow); + using var resizedImage = this.BoolArrayToSKBitmap(result, barcode.Height); + var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); + this.DrawBarcode(png, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation); - var barcodeElement = new Barcode + if (barcode.PrintInterpretationLine) { - BarWidth = barcode.ModuleWidth, - BackColor = Color.Transparent, - Height = barcode.Height + labelHeight, - IncludeLabel = barcode.PrintInterpretationLine, - LabelPosition = barcode.PrintInterpretationLineAboveCode ? LabelPositions.TOPCENTER : LabelPositions.BOTTOMCENTER, - LabelFont = labelFont, - AlternateLabel = interpretation - }; - - using var image = barcodeElement.Encode(TYPE.CODE39Extended, content); - this.DrawBarcode(this.GetImageData(image), barcode.Height, image.Width, barcode.FieldOrigin != null, x, y, labelHeightOffset, barcode.FieldOrientation); + float labelFontSize = Math.Min(barcode.ModuleWidth * 10f, 100f); + var labelTypeFace = options.FontLoader("A"); + var labelFont = new SKFont(labelTypeFace, labelFontSize); + this.DrawInterpretationLine(interpretation, labelFont, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation, barcode.PrintInterpretationLineAboveCode, options); + } } } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode93ElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode93ElementDrawer.cs new file mode 100644 index 00000000..ae4f951c --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Barcode93ElementDrawer.cs @@ -0,0 +1,49 @@ +using BinaryKits.Zpl.Label.Elements; +using SkiaSharp; +using System; +using ZXing.OneD; + +namespace BinaryKits.Zpl.Viewer.ElementDrawers +{ + public class Barcode93ElementDrawer : BarcodeDrawerBase + { + /// + public override bool CanDraw(ZplElementBase element) + { + return element is ZplBarcode93; + } + + /// + public override void Draw(ZplElementBase element) + { + Draw(element, new DrawerOptions()); + } + + /// + public override void Draw(ZplElementBase element, DrawerOptions options) + { + if (element is ZplBarcode93 barcode) + { + float x = barcode.PositionX; + float y = barcode.PositionY; + + var content = barcode.Content; + + var writer = new Code93Writer(); + var result = writer.encode(content); + using var resizedImage = this.BoolArrayToSKBitmap(result, barcode.Height, barcode.ModuleWidth); + var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); + this.DrawBarcode(png, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation); + + if (barcode.PrintInterpretationLine) + { + float labelFontSize = Math.Min(barcode.ModuleWidth * 10f, 100f); + var labelTypeFace = options.FontLoader("A"); + var labelFont = new SKFont(labelTypeFace, labelFontSize); + this.DrawInterpretationLine(content, labelFont, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation, barcode.PrintInterpretationLineAboveCode, options); + } + + } + } + } +} diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeDrawerBase.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeDrawerBase.cs index 25ddbcf2..eead6e13 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeDrawerBase.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeDrawerBase.cs @@ -1,81 +1,148 @@ using SkiaSharp; -using System.Drawing; -using System.Drawing.Imaging; -using System.IO; +using SkiaSharp.HarfBuzz; +using System; +using System.Collections.Generic; +using System.Linq; using ZXing.Common; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Base clase for Barcode element drawers + /// public abstract class BarcodeDrawerBase : ElementDrawerBase { - public byte[] GetImageData(Image image) + /// + /// Minimum acceptable magin between a barcode and its interpretation line, in pixels + /// + protected const float MIN_LABEL_MARGIN = 5f; + + protected void DrawBarcode( + byte[] barcodeImageData, + float x, + float y, + int barcodeWidth, + int barcodeHeight, + bool useFieldOrigin, + Label.FieldOrientation fieldOrientation) { - using (var memoryStream = new MemoryStream()) + using (new SKAutoCanvasRestore(this._skCanvas)) { - image.Save(memoryStream, ImageFormat.Png); - return memoryStream.ToArray(); + SKMatrix matrix = this.GetRotationMatrix(x, y, barcodeWidth, barcodeHeight, useFieldOrigin, fieldOrientation); + + if (!useFieldOrigin) + { + y -= barcodeHeight; + } + + if (matrix != SKMatrix.Empty) + { + this._skCanvas.SetMatrix(matrix); + } + + this._skCanvas.DrawBitmap(SKBitmap.Decode(barcodeImageData), x, y); } } - public void DrawBarcode( - byte[] barcodeImageData, - int barcodeHeight, - int barcodeWidth, - bool useFieldOrigin, + protected void DrawInterpretationLine( + string interpretation, + SKFont skFont, float x, float y, - int labelHeightOffset, - Label.FieldOrientation fieldOrientation) + int barcodeWidth, + int barcodeHeight, + bool useFieldOrigin, + Label.FieldOrientation fieldOrientation, + bool printInterpretationLineAboveCode, + DrawerOptions options) { using (new SKAutoCanvasRestore(this._skCanvas)) { - SKMatrix matrix = SKMatrix.Empty; + using var skPaint = new SKPaint(skFont); + skPaint.IsAntialias = options.Antialias; + + SKMatrix matrix = this.GetRotationMatrix(x, y, barcodeWidth, barcodeHeight, useFieldOrigin, fieldOrientation); - if (useFieldOrigin) + if (matrix != SKMatrix.Empty) { - switch (fieldOrientation) - { - case Label.FieldOrientation.Rotated90: - matrix = SKMatrix.CreateRotationDegrees(90, x + barcodeHeight / 2, y + barcodeHeight / 2); - break; - case Label.FieldOrientation.Rotated180: - matrix = SKMatrix.CreateRotationDegrees(180, x + barcodeWidth / 2, y + barcodeHeight / 2); - break; - case Label.FieldOrientation.Rotated270: - matrix = SKMatrix.CreateRotationDegrees(270, x + barcodeWidth / 2, y + barcodeWidth / 2); - break; - case Label.FieldOrientation.Normal: - break; - } + this._skCanvas.SetMatrix(matrix); } - else + + var textBounds = new SKRect(); + skPaint.MeasureText(interpretation, ref textBounds); + + x += (barcodeWidth - textBounds.Width) / 2; + if (!useFieldOrigin) { - switch (fieldOrientation) - { - case Label.FieldOrientation.Rotated90: - matrix = SKMatrix.CreateRotationDegrees(90, x, y); - break; - case Label.FieldOrientation.Rotated180: - matrix = SKMatrix.CreateRotationDegrees(180, x, y); - break; - case Label.FieldOrientation.Rotated270: - matrix = SKMatrix.CreateRotationDegrees(270, x, y); - break; - case Label.FieldOrientation.Normal: - break; - } y -= barcodeHeight; } - y -= labelHeightOffset; + float margin = Math.Max((skFont.Spacing - textBounds.Height) / 2, MIN_LABEL_MARGIN); - if (matrix != SKMatrix.Empty) + if (printInterpretationLineAboveCode) { - this._skCanvas.SetMatrix(matrix); + this._skCanvas.DrawShapedText(interpretation, x, y - margin, skPaint); } + else + { + this._skCanvas.DrawShapedText(interpretation, x, y + barcodeHeight + textBounds.Height + margin, skPaint); + } + } + } - this._skCanvas.DrawBitmap(SKBitmap.Decode(barcodeImageData), x, y); + protected SKMatrix GetRotationMatrix(float x, float y, int width, int height, bool useFieldOrigin, Label.FieldOrientation fieldOrientation) + { + SKMatrix matrix = SKMatrix.Empty; + + if (useFieldOrigin) + { + switch (fieldOrientation) + { + case Label.FieldOrientation.Rotated90: + matrix = SKMatrix.CreateRotationDegrees(90, x + height / 2, y + height / 2); + break; + case Label.FieldOrientation.Rotated180: + matrix = SKMatrix.CreateRotationDegrees(180, x + width / 2, y + height / 2); + break; + case Label.FieldOrientation.Rotated270: + matrix = SKMatrix.CreateRotationDegrees(270, x + width / 2, y + width / 2); + break; + case Label.FieldOrientation.Normal: + break; + } + } + else + { + switch (fieldOrientation) + { + case Label.FieldOrientation.Rotated90: + matrix = SKMatrix.CreateRotationDegrees(90, x, y); + break; + case Label.FieldOrientation.Rotated180: + matrix = SKMatrix.CreateRotationDegrees(180, x, y); + break; + case Label.FieldOrientation.Rotated270: + matrix = SKMatrix.CreateRotationDegrees(270, x, y); + break; + case Label.FieldOrientation.Normal: + break; + } + } + + return matrix; + } + + protected SKBitmap BoolArrayToSKBitmap(bool[] array, int height, int moduleWidth = 1) + { + using var image = new SKBitmap(array.Length, 1); + + for (int col = 0; col < array.Length; col++) + { + var color = array[col] ? SKColors.Black : SKColors.Transparent; + image.SetPixel(col, 0, color); } + + return image.Resize(new SKSizeI(image.Width * moduleWidth, height), SKFilterQuality.None); } protected SKBitmap BitMatrixToSKBitmap(BitMatrix matrix, int pixelScale) @@ -93,5 +160,27 @@ protected SKBitmap BitMatrixToSKBitmap(BitMatrix matrix, int pixelScale) return image.Resize(new SKSizeI(image.Width * pixelScale, image.Height * pixelScale), SKFilterQuality.None); } + + protected bool[] AdjustWidths(bool[] array, int wide, int narrow) + { + List result = new List(); + var last = true; + var count = 0; + foreach (var current in array) + { + if (current != last) + { + result.AddRange(Enumerable.Repeat(last, count == 1 ? narrow : wide)); + last = current; + count = 0; + } + + count += 1; + } + + result.AddRange(Enumerable.Repeat(last, narrow)); + + return result.ToArray(); + } } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeEAN13ElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeEAN13ElementDrawer.cs index 5a3e5031..c0ca0b1f 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeEAN13ElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/BarcodeEAN13ElementDrawer.cs @@ -1,23 +1,29 @@ -using BarcodeLib; using BinaryKits.Zpl.Label.Elements; -using BinaryKits.Zpl.Viewer.Helpers; using SkiaSharp; using System; -using System.Drawing; +using ZXing.OneD; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for EAN-13 Barcode elements + /// public class BarcodeEAN13ElementDrawer : BarcodeDrawerBase { - public override bool CanDraw(ZplElementBase element) + private static readonly bool[] guards = new bool[95]; + + static BarcodeEAN13ElementDrawer() { - return element is ZplBarcodeEan13; + foreach (int idx in new int[] { 0, 2, 46, 48, 92, 94 }) + { + guards[idx] = true; + } } /// - public override void Draw(ZplElementBase element) + public override bool CanDraw(ZplElementBase element) { - Draw(element, new DrawerOptions()); + return element is ZplBarcodeEan13; } /// @@ -32,37 +38,90 @@ public override void Draw(ZplElementBase element, DrawerOptions options) content = content.PadLeft(12, '0').Substring(0, 12); var interpretation = content; - if (barcode.PrintInterpretationLineAboveCode) + int checksum = 0; + for (int i = 0; i < 12; i++) + { + checksum += (content[i] - 48) * (9 - i % 2 * 2); + } + interpretation = string.Format("{0}{1}", interpretation, checksum % 10); + + var writer = new EAN13Writer(); + var result = writer.encode(content); + using var resizedImage = this.BoolArrayToSKBitmap(result, barcode.Height, barcode.ModuleWidth); + var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); + this.DrawBarcode(png, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation); + + if (barcode.PrintInterpretationLine) { - int checksum = 0; - for (int i = 0; i < 12; i++) + float labelFontSize = Math.Min(barcode.ModuleWidth * 10f, 100f); + var labelTypeFace = options.FontLoader("A"); + var labelFont = new SKFont(labelTypeFace, labelFontSize); + if (barcode.PrintInterpretationLineAboveCode) + { + this.DrawInterpretationLine(interpretation, labelFont, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation, true, options); + } + else { - checksum += (content[i] - 48) * (9 - i % 2 * 2); + this.DrawEAN13InterpretationLine(interpretation, labelFont, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation, barcode.ModuleWidth, options); } - interpretation = string.Format("{0}{1}", interpretation, checksum % 10); } - float labelFontSize = Math.Min(barcode.ModuleWidth * 7.2f, 72f); - var labelTypeFace = options.FontLoader("A"); - var labelFont = new SKFont(labelTypeFace, labelFontSize).ToSystemDrawingFont(); - int labelHeight = barcode.PrintInterpretationLine ? labelFont.Height : 0; - int labelHeightOffset = barcode.PrintInterpretationLineAboveCode ? labelHeight : 0; + } + } + + private void DrawEAN13InterpretationLine( + string interpretation, + SKFont skFont, + float x, + float y, + int barcodeWidth, + int barcodeHeight, + bool useFieldOrigin, + Label.FieldOrientation fieldOrientation, + int moduleWidth, + DrawerOptions options) + { + using (new SKAutoCanvasRestore(this._skCanvas)) + { + using var skPaint = new SKPaint(skFont); + skPaint.IsAntialias = options.Antialias; + + SKMatrix matrix = this.GetRotationMatrix(x, y, barcodeWidth, barcodeHeight, useFieldOrigin, fieldOrientation); + + if (matrix != SKMatrix.Empty) + { + this._skCanvas.SetMatrix(matrix); + } + + var textBounds = new SKRect(); + skPaint.MeasureText(interpretation, ref textBounds); + + if (!useFieldOrigin) + { + y -= barcodeHeight; + } + + float margin = Math.Max((skFont.Spacing - textBounds.Height) / 2, MIN_LABEL_MARGIN); + int spacing = moduleWidth * 7; + + using var guardImage = this.BoolArrayToSKBitmap(guards, (int)(margin + textBounds.Height / 2), moduleWidth); + var guardPng = guardImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); + this._skCanvas.DrawBitmap(SKBitmap.Decode(guardPng), x, y + barcodeHeight); - var barcodeElement = new Barcode + for (int i = 0; i < interpretation.Length; i++) { - BarWidth = barcode.ModuleWidth, - BackColor = Color.White, - Height = barcode.Height + labelHeight, - IncludeLabel = barcode.PrintInterpretationLine, - LabelPosition = barcode.PrintInterpretationLineAboveCode ? LabelPositions.TOPCENTER : LabelPositions.BOTTOMCENTER, - LabelFont = labelFont, - AlternateLabel = interpretation, - StandardizeLabel = !barcode.PrintInterpretationLineAboveCode - }; - - using var image = barcodeElement.Encode(TYPE.EAN13, content); - this.DrawBarcode(this.GetImageData(image), barcode.Height, image.Width, barcode.FieldOrigin != null, x, y, labelHeightOffset, barcode.FieldOrientation); + string digit = interpretation[i].ToString(); + var digitBounds = new SKRect(); + skPaint.MeasureText(digit, ref digitBounds); + this._skCanvas.DrawText(digit, x - (spacing + digitBounds.Width) / 2 - moduleWidth, y + barcodeHeight + textBounds.Height + margin, skPaint); + x += spacing; + if (i == 0 || i == 6) + { + x += moduleWidth * 4; + } + } } } + } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/DataMatrixElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/DataMatrixElementDrawer.cs index d2cf127e..fc6f70a5 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/DataMatrixElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/DataMatrixElementDrawer.cs @@ -1,14 +1,19 @@ using BinaryKits.Zpl.Label.Elements; using SkiaSharp; -using System.Collections.Generic; +using System.Text.RegularExpressions; using ZXing; using ZXing.Datamatrix; using ZXing.Datamatrix.Encoder; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Data Matrix Barcode elements + /// public class DataMatrixElementDrawer : BarcodeDrawerBase { + private static readonly Regex gs1Regex = new Regex(@"^_1(.+)$", RegexOptions.Compiled); + /// public override bool CanDraw(ZplElementBase element) { @@ -29,18 +34,33 @@ 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; + + Match gs1Match = gs1Regex.Match(content); + if (gs1Match.Success) + { + content = gs1Match.Groups[1].Value; + gs1Mode = true; + } + var writer = new DataMatrixWriter(); - var hints = new Dictionary { - { EncodeHintType.DATA_MATRIX_SHAPE, SymbolShapeHint.FORCE_SQUARE } + var encodingOptions = new DatamatrixEncodingOptions() + { + SymbolShape = SymbolShapeHint.FORCE_SQUARE, + CompactEncoding = gs1Mode, + GS1Format = gs1Mode }; - var result = writer.encode(dataMatrix.Content, BarcodeFormat.DATA_MATRIX, 0, 0, hints); + var result = writer.encode(content, BarcodeFormat.DATA_MATRIX, 0, 0, encodingOptions.Hints); using var resizedImage = this.BitMatrixToSKBitmap(result, dataMatrix.Height); { var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); - this.DrawBarcode(png, resizedImage.Height, resizedImage.Width, dataMatrix.FieldOrigin != null, x, y, 0, dataMatrix.FieldOrientation); + this.DrawBarcode(png, x, y, resizedImage.Width, resizedImage.Height, dataMatrix.FieldOrigin != null, dataMatrix.FieldOrientation); } } } + } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/DrawerOptions.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/DrawerOptions.cs index 38c0bbf3..ad2f5245 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/DrawerOptions.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/DrawerOptions.cs @@ -1,6 +1,7 @@ using SkiaSharp; using System; using System.Diagnostics; +using System.Linq; namespace BinaryKits.Zpl.Viewer.ElementDrawers { @@ -10,25 +11,115 @@ public class DrawerOptions public SKEncodedImageFormat RenderFormat { get; set; } = SKEncodedImageFormat.Png; + public int RenderQuality { get; set; } = 80; + /// /// Applies label over a white background after rendering all elements /// public bool OpaqueBackground { get; set; } = false; - - public int RenderQuality { get; set; } = 80; + + /// + /// Renders the label as pdf + /// + public bool PdfOutput { get; set; } = false; public bool ReplaceDashWithEnDash { get; set; } = true; + public bool Antialias { get; set; } = true; + public static Func DefaultFontLoader = fontName => { var typeface = SKTypeface.Default; + var skFontManager = SKFontManager.Default; + var fontFamilies = skFontManager.FontFamilies; + if (fontName == "0") { - //typeface = SKTypeface.FromFile(@"swiss-721-black-bt.ttf"); - typeface = SKTypeface.FromFamilyName("Helvetica", SKFontStyleWeight.Bold, SKFontStyleWidth.SemiCondensed, SKFontStyleSlant.Upright); + if (fontFamilies.Contains("Helvetica")) + { + typeface = SKTypeface.FromFamilyName( + "Helvetica", + SKFontStyleWeight.Bold, + SKFontStyleWidth.SemiCondensed, + SKFontStyleSlant.Upright + ); + } + else if (fontFamilies.Contains("Roboto Condensed")) + { + typeface = SKTypeface.FromFamilyName( + "Roboto Condensed", + SKFontStyleWeight.Bold, + SKFontStyleWidth.Normal, + SKFontStyleSlant.Upright + ); + } + else if (fontFamilies.Contains("Swis721 BT")) + { + //Note: Zebra Swis721 BT (tt0003m_.ttf) is not as bold and condensed as Labelary + //Note: swiss-721-bt-bold.ttf is not as condensed as Labelary + //typeface = SKTypeface.FromFile(@"swiss-721-black-bt.ttf"); + typeface = SKTypeface.FromFamilyName( + "Swis721 BT" + ); + } + else if (fontFamilies.Contains("Arial")) + { + typeface = SKTypeface.FromFamilyName( + "Arial", + SKFontStyleWeight.Bold, + SKFontStyleWidth.Condensed, + SKFontStyleSlant.Upright + ); + } + else + { + //let the system provide a fallback for Helvetica + typeface = SKTypeface.FromFamilyName( + "Helvetica", + SKFontStyleWeight.Bold, + SKFontStyleWidth.SemiCondensed, + SKFontStyleSlant.Upright + ); + } } else { - typeface = SKTypeface.FromFamilyName("DejaVu Sans Mono", SKFontStyleWeight.Normal, SKFontStyleWidth.Normal, SKFontStyleSlant.Upright); + if (fontFamilies.Contains("DejaVu Sans Mono")) + { + typeface = SKTypeface.FromFamilyName( + "DejaVu Sans Mono", + SKFontStyleWeight.Normal, + SKFontStyleWidth.Normal, + SKFontStyleSlant.Upright + ); + } + else if (fontFamilies.Contains("Courier New")) + { + typeface = SKTypeface.FromFamilyName( + "Courier New", + SKFontStyleWeight.Medium, + SKFontStyleWidth.Normal, + SKFontStyleSlant.Upright + ); + } + else if (fontFamilies.Contains("Courier")) + { + typeface = SKTypeface.FromFamilyName( + "Courier", + SKFontStyleWeight.Medium, + SKFontStyleWidth.Normal, + SKFontStyleSlant.Upright + ); + } + else + { + //let the system provide a fallback for DejaVu + typeface = SKTypeface.FromFamilyName( + "DejaVu Sans Mono", + SKFontStyleWeight.Normal, + SKFontStyleWidth.Normal, + SKFontStyleSlant.Upright + ); + } } return typeface; diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/ElementDrawerBase.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/ElementDrawerBase.cs index 7f39c2b1..3947c232 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/ElementDrawerBase.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/ElementDrawerBase.cs @@ -25,9 +25,24 @@ public virtual bool IsReverseDraw(ZplElementBase element) { return false; } + + /// + public virtual bool IsWhiteDraw(ZplElementBase element) + { + return false; + } + + /// + public virtual bool ForceBitmapDraw(ZplElementBase element) + { + return false; + } /// - public abstract void Draw(ZplElementBase element); + public virtual void Draw(ZplElementBase element) + { + Draw(element, new DrawerOptions()); + } /// public virtual void Draw(ZplElementBase element, DrawerOptions options = null) diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/FieldBlockElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/FieldBlockElementDrawer.cs index d7a5f06b..5e59cdb9 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/FieldBlockElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/FieldBlockElementDrawer.cs @@ -1,8 +1,10 @@ using BinaryKits.Zpl.Label; using BinaryKits.Zpl.Label.Elements; using BinaryKits.Zpl.Viewer.Helpers; + using SkiaSharp; using SkiaSharp.HarfBuzz; + using System; using System.Collections.Generic; using System.Linq; @@ -10,6 +12,9 @@ namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Field Block elements + /// public class FieldBlockElementDrawer : ElementDrawerBase { /// @@ -18,6 +23,7 @@ public override bool CanDraw(ZplElementBase element) return element is ZplFieldBlock; } + /// public override bool IsReverseDraw(ZplElementBase element) { if (element is ZplFieldBlock fieldBlock) @@ -28,12 +34,6 @@ public override bool IsReverseDraw(ZplElementBase element) return false; } - /// - public override void Draw(ZplElementBase element) - { - Draw(element, new DrawerOptions()); - } - /// public override void Draw(ZplElementBase element, DrawerOptions options) { @@ -61,7 +61,11 @@ public override void Draw(ZplElementBase element, DrawerOptions options) } var skFont = new SKFont(typeface, fontSize, scaleX); - using var skPaint = new SKPaint(skFont); + using var skPaint = new SKPaint(skFont) + { + IsAntialias = options.Antialias + }; + var textBoundBaseline = new SKRect(); skPaint.MeasureText("X", ref textBoundBaseline); @@ -79,7 +83,7 @@ public override void Draw(ZplElementBase element, DrawerOptions options) if (fieldBlock.FieldTypeset != null) { - totalHeight = lineHeight * (fieldBlock.MaxLineCount-1) + textBoundBaseline.Height; + totalHeight = lineHeight * (fieldBlock.MaxLineCount - 1) + textBoundBaseline.Height; y -= totalHeight; } @@ -151,6 +155,11 @@ public override void Draw(ZplElementBase element, DrawerOptions options) break; } + if (fieldBlock.ReversePrint) + { + skPaint.BlendMode = SKBlendMode.Xor; + } + this._skCanvas.DrawShapedText(textLine, x, y, skPaint); y += lineHeight; } @@ -167,7 +176,7 @@ private IEnumerable WordWrap(string text, SKFont font, int maxWidth) var words = new Stack(text.Split(new[] { ' ' }, StringSplitOptions.None).Reverse()); var line = new StringBuilder(); float width = 0; - while(words.Any()) + while (words.Any()) { var word = words.Pop(); if (word.Contains(@"\&")) diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicBoxElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicBoxElementDrawer.cs index 001aa4f1..3a327c88 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicBoxElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicBoxElementDrawer.cs @@ -1,4 +1,5 @@ -using BinaryKits.Zpl.Label.Elements; +using BinaryKits.Zpl.Label; +using BinaryKits.Zpl.Label.Elements; using SkiaSharp; using System; @@ -22,8 +23,28 @@ public override bool IsReverseDraw(ZplElementBase element) return false; } + public override bool IsWhiteDraw(ZplElementBase element) + { + if (element is ZplGraphicBox graphicBox) + { + return graphicBox.LineColor == LineColor.White; + } + + return false; + } + + public override bool ForceBitmapDraw(ZplElementBase element) + { + if (element is ZplGraphicBox graphicBox && graphicBox.CornerRounding > 0) + { + return true; + } + + return false; + } + /// - public override void Draw(ZplElementBase element) + public override void Draw(ZplElementBase element, DrawerOptions options) { if (element is ZplGraphicBox graphicBox) { @@ -41,56 +62,84 @@ public override void Draw(ZplElementBase element) height1 = border1; } - var offsetX = border1 / 2.0f; - var offsetY = border1 / 2.0f; - - var x = graphicBox.PositionX + offsetX; - var y = graphicBox.PositionY + offsetY; + //border cant be bigger or equal to width or height + if (border1 > (width1 / 2) && width1 <= height1) + { + border1 = (int)Math.Ceiling((float)width1 / 2); + } + + if (border1 > (height1 / 2) && height1 <= width1) + { + border1 = (int)Math.Ceiling((float)height1 / 2); + } - if (graphicBox.FieldTypeset != null) + //make sure border is never smaller than 1 + if (border1 < 1) { - y -= height1; + border1 = 1; + } - //Fallback - if (y < offsetY) + //if the border is thick, the rounding is off, so we need to build that for each increment + var lastPrintedBorder = border1; + for (var border2 = border1; border2 >= 1; border2--) + { + //skip the parts that have overlap from the previous draw + if (border2 != 1 && border2 != lastPrintedBorder && border2 > (lastPrintedBorder / 2)) { - y = offsetY; + continue; } - } + + lastPrintedBorder = border2; - var width = width1 - border1; - var height = height1 - border1; + var offsetX = border2 / 2.0f; + var offsetY = border2 / 2.0f; - using var skPaint = new SKPaint(); - skPaint.Style = SKPaintStyle.Stroke; - skPaint.StrokeCap = SKStrokeCap.Square; - skPaint.Color = SKColors.Black; - skPaint.StrokeWidth = border1; + var x = graphicBox.PositionX + offsetX; + var y = graphicBox.PositionY + offsetY; - if (graphicBox.LineColor == Label.LineColor.White) - { - skPaint.Color = SKColors.Transparent; - } + if (graphicBox.FieldTypeset != null) + { + y -= height1; - var cornerRadius = (graphicBox.CornerRounding / 8.0f) * (Math.Min(width1, height1) / 2.0f); - if (cornerRadius == 0) - { - this._skCanvas.DrawRect(x, y, width, height, skPaint); - return; - } + //Fallback + if (y < offsetY) + { + y = offsetY; + } + } + + var width = width1 - border2; + var height = height1 - border2; - //using var test = new SkiaSharp.SKRoundRect(new SkiaSharp.SKRect(x, y, width + x, height + y), cornerRadius); - //test.Deflate(5,5); - //test.Inflate(10, 10); - //test.Offset(offsetX, offsetY); - //test.SetNinePatch(new SkiaSharp.SKRect(x + 1, y, width + x, height + y), 40, 3,4, 5); - //test.SetOval(new SkiaSharp.SKRect(x - 50, y-10, width + x -10, height + y - 10)); - //this._skCanvas.DrawRoundRect(test, this._skPaint); + using var skPaint = new SKPaint() + { + IsAntialias = options.Antialias, + Style = SKPaintStyle.Stroke, + StrokeCap = SKStrokeCap.Square, + Color = SKColors.Black, + StrokeWidth = border2 + }; + + if (graphicBox.LineColor == LineColor.White) + { + skPaint.Color = SKColors.White; + } - //TODO:Some corner radius is too much - //^XA^FO50,50^GB100,100,120,B,1^FS^XZ + var cornerRadius = (graphicBox.CornerRounding / 8.0f) * (Math.Min(width1, height1) / 2.0f); - this._skCanvas.DrawRoundRect(x, y, width, height, cornerRadius, cornerRadius, skPaint); + if (cornerRadius == 0) + { + if (graphicBox.ReversePrint) + { + skPaint.BlendMode = SKBlendMode.Xor; + } + + this._skCanvas.DrawRect(x, y, width, height, skPaint); + return; + } + + this._skCanvas.DrawRoundRect(x, y, width, height, cornerRadius, cornerRadius, skPaint); + } } } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicCircleElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicCircleElementDrawer.cs index 57a35fa5..44498f44 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicCircleElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicCircleElementDrawer.cs @@ -1,4 +1,5 @@ -using BinaryKits.Zpl.Label.Elements; +using BinaryKits.Zpl.Label; +using BinaryKits.Zpl.Label.Elements; using SkiaSharp; namespace BinaryKits.Zpl.Viewer.ElementDrawers @@ -10,9 +11,29 @@ public override bool CanDraw(ZplElementBase element) { return element is ZplGraphicCircle; } + + public override bool IsReverseDraw(ZplElementBase element) + { + if (element is ZplGraphicCircle graphicCircle) + { + return graphicCircle.ReversePrint; + } + + return false; + } + + public override bool IsWhiteDraw(ZplElementBase element) + { + if (element is ZplGraphicCircle graphicCircle) + { + return graphicCircle.LineColor == LineColor.White; + } + + return false; + } /// - public override void Draw(ZplElementBase element) + public override void Draw(ZplElementBase element, DrawerOptions options) { if (element is ZplGraphicCircle graphicCircle) { @@ -24,10 +45,17 @@ public override void Draw(ZplElementBase element) border = radius; } - using var skPaint = new SKPaint(); - skPaint.Style = SKPaintStyle.Stroke; - skPaint.Color = SKColors.Black; - skPaint.StrokeWidth = border; + using var skPaint = new SKPaint() + { + IsAntialias = options.Antialias, + Style = SKPaintStyle.Stroke, + Color = SKColors.Black, + StrokeWidth = border + }; + if (graphicCircle.LineColor == LineColor.White) + { + skPaint.Color = SKColors.White; + } var halfBorderThickness = border / 2.0f; @@ -47,6 +75,11 @@ public override void Draw(ZplElementBase element) y = radius; } } + + if (graphicCircle.ReversePrint) + { + skPaint.BlendMode = SKBlendMode.Xor; + } this._skCanvas.DrawCircle(x, y, radiusMinusBorder, skPaint); } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs index 350ddfd6..dd98927c 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/GraphicFieldElementDrawer.cs @@ -1,10 +1,12 @@ using BinaryKits.Zpl.Label.Elements; using BinaryKits.Zpl.Label.Helpers; -using BinaryKits.Zpl.Viewer.Helpers; using SkiaSharp; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Graphic Field elements + /// public class GraphicFieldElementDrawer : ElementDrawerBase { /// diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/IElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/IElementDrawer.cs index 50e31472..cafc9e3a 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/IElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/IElementDrawer.cs @@ -3,6 +3,9 @@ namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Public interface for element drawers + /// public interface IElementDrawer { /// @@ -15,18 +18,32 @@ void Prepare( SKCanvas skCanvas); /// - /// Check the drawer can draw this element + /// Check if the drawer can draw this element /// /// /// bool CanDraw(ZplElementBase element); /// - /// Element require reverse draw + /// Element requires reverse draw /// /// /// bool IsReverseDraw(ZplElementBase element); + + /// + /// Element is white + /// + /// + /// + bool IsWhiteDraw(ZplElementBase element); + + /// + /// Element needs to be drawn in bitmap mode + /// + /// + /// + bool ForceBitmapDraw(ZplElementBase element); /// /// Draw the element diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/ImageMoveElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/ImageMoveElementDrawer.cs index 00eb3219..73940668 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/ImageMoveElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/ImageMoveElementDrawer.cs @@ -3,6 +3,9 @@ namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Image Move elements + /// public class ImageMoveElementDrawer : ElementDrawerBase { /// diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/Interleaved2of5BarcodeDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Interleaved2of5BarcodeDrawer.cs index b0eef101..2bf9f073 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/Interleaved2of5BarcodeDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/Interleaved2of5BarcodeDrawer.cs @@ -1,12 +1,13 @@ -using BarcodeLib; using BinaryKits.Zpl.Label.Elements; -using BinaryKits.Zpl.Viewer.Helpers; using SkiaSharp; using System; -using System.Drawing; +using ZXing.OneD; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Interleaved 2 of 5 Barcode elements + /// public class Interleaved2of5BarcodeDrawer : BarcodeDrawerBase { /// @@ -15,12 +16,6 @@ public override bool CanDraw(ZplElementBase element) return element is ZplBarcodeInterleaved2of5; } - /// - public override void Draw(ZplElementBase element) - { - Draw(element, new DrawerOptions()); - } - /// public override void Draw(ZplElementBase element, DrawerOptions options) { @@ -29,24 +24,22 @@ public override void Draw(ZplElementBase element, DrawerOptions options) float x = barcode.PositionX; float y = barcode.PositionY; - float labelFontSize = Math.Min(barcode.ModuleWidth * 7.2f, 72f); - var labelTypeFace = options.FontLoader("A"); - var labelFont = new SKFont(labelTypeFace, labelFontSize).ToSystemDrawingFont(); - int labelHeight = barcode.PrintInterpretationLine ? labelFont.Height : 0; - int labelHeightOffset = barcode.PrintInterpretationLineAboveCode ? labelHeight : 0; + var writer = new ITFWriter(); + var result = writer.encode(barcode.Content); + int narrow = barcode.ModuleWidth; + int wide = (int)Math.Floor(barcode.WideBarToNarrowBarWidthRatio * narrow); + result = this.AdjustWidths(result, wide, narrow); + using var resizedImage = this.BoolArrayToSKBitmap(result, barcode.Height); + var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); + this.DrawBarcode(png, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation); - var barcodeElement = new Barcode + if (barcode.PrintInterpretationLine) { - BarWidth = barcode.ModuleWidth, - BackColor = Color.Transparent, - Height = barcode.Height + labelHeight, - IncludeLabel = barcode.PrintInterpretationLine, - LabelPosition = barcode.PrintInterpretationLineAboveCode ? LabelPositions.TOPCENTER : LabelPositions.BOTTOMCENTER, - LabelFont = labelFont - }; - - using var image = barcodeElement.Encode(TYPE.Interleaved2of5, barcode.Content); - this.DrawBarcode(this.GetImageData(image), barcode.Height, image.Width, barcode.FieldOrigin != null, x, y, labelHeightOffset, barcode.FieldOrientation); + float labelFontSize = Math.Min(barcode.ModuleWidth * 10f, 100f); + var labelTypeFace = options.FontLoader("A"); + var labelFont = new SKFont(labelTypeFace, labelFontSize); + this.DrawInterpretationLine(barcode.Content, labelFont, x, y, resizedImage.Width, resizedImage.Height, barcode.FieldOrigin != null, barcode.FieldOrientation, barcode.PrintInterpretationLineAboveCode, options); + } } } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/MaxiCodeElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/MaxiCodeElementDrawer.cs new file mode 100644 index 00000000..b09b6755 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/MaxiCodeElementDrawer.cs @@ -0,0 +1,161 @@ +using BinaryKits.Zpl.Label.Elements; +using BinaryKits.Zpl.Viewer.Helpers; +using CoelWu.Zint.Net; +using System; +using System.IO; +using System.Drawing; +using System.Drawing.Drawing2D; + +namespace BinaryKits.Zpl.Viewer.ElementDrawers +{ + public class MaxiCodeElementDrawer : BarcodeDrawerBase + { + /// + public override bool CanDraw(ZplElementBase element) + { + return element is ZplMaxiCode; + } + + /// + public override void Draw(ZplElementBase element) + { + if (element is ZplMaxiCode maxiCode) + { + var maxiBarcode = new ZintNetLib(); + maxiBarcode.MaxicodeMode = ConvertMaxiCodeMode(maxiCode.Mode); + var content = maxiCode.Content; + + try + { + //replace hex items before mode 2 and mode 3 specific string manipulation + if (maxiCode.UseHexadecimalIndicator) + { + content = content.ReplaceHexEscapes(); + } + + if (maxiBarcode.MaxicodeMode == MaxicodeMode.Mode2) + { + //ZPL mode 2: + //^FD[aaa][bbb][ccccc][dddd]_5B)>_1E[ee]_1D[ff]1Z + //^FD002840100450000_5B)>_1E01_1D961Z + // = aaabbbcccccdddd + // aaa = three-digit class of service + // bbb = three-digit country code + // ccccc = five-digit zip code + // dddd = four-digit zip code extension (if none exists, four zeros (0000) must be entered) + // ee = 01 + // ff = year number + //ZintNetLib structure: + //^FD_5B)>_1E[ee]_1D[ff][ccccc][dddd]_1D[bbb]_1D[aaa]_1D1Z + //^FD_5B)>_1E01_1D96100450000_1D840_1D002_1D1Z + //Note: Country code 630 is not allowed in this library for mode 2 + + var replace = content.Substring(0, 24); + var aaa = content.Substring(0, 3); + var bbb = content.Substring(3, 3); + var ccccc = content.Substring(6, 5); + var dddd = content.Substring(11, 4); + var ee = content.Substring(19, 2); + var ff = content.Substring(22, 2); + var newString = $"\x5B)>\x1E{ee}\x1D{ff}{ccccc}{dddd}\x1D{bbb}\x1D{aaa}\x1D"; + content = content.Replace(replace, newString); + } + else if (maxiBarcode.MaxicodeMode == MaxicodeMode.Mode3) + { + //ZPL mode 3: + //^FD[aaa][bbb][cccccc]_5B)>_1E[ee]_1D[ff]1Z + //^FD066826RS19 _5B)>_1E01_1D961Z + // = aaabbbcccccc + // aaa = three-digit class of service + // bbb = three-digit country code + // ccccc = six-digit zip code (A through Z or 0 to 9) + // ee = 01 + // ff = year number + //ZintNetLib structure: + //^FD_5B)>_1E[ee]_1D[ff][cccccc]_1D[bbb]_1D[aaa]_1D1Z + //^FD_5B)>_1E01_1D96RS19 _1D826_1D066_1D1Z + + var replace = content.Substring(0, 21); + var aaa = content.Substring(0, 3); + var bbb = content.Substring(3, 3); + var cccccc = content.Substring(6, 6); + var ee = content.Substring(16, 2); + var ff = content.Substring(19, 2); + var newString = $"\x5B)>\x1E{ee}\x1D{ff}{cccccc}\x1D{bbb}\x1D{aaa}\x1D"; + content = content.Replace(replace, newString); + } + + maxiBarcode.CreateBarcode("Maxicode(ISO 16023)", content); + } + catch + { + // Do nothing + } + + //^BD2,1,1 + if (maxiBarcode.IsValid) + { + var bitmap = new Bitmap(1000, 1000); + var graphics = Graphics.FromImage(bitmap); + graphics.Clear(Color.White); + + maxiBarcode.Rotation = 0; + maxiBarcode.Multiplier = 2; + //Note: Position and Total are not supported in this MaxiCode library + maxiBarcode.DrawBarcode(graphics, new Point(-6, -6)); + + //Linux container fix, redraw the circles (variation of: CoelWu.Zint.Net.ZintNetLib.DrawBarcode) + graphics.SmoothingMode = SmoothingMode.AntiAlias; + float num3 = 13.64f * 7f; + float num4 = 13.43f * 7f; + float num5 = 0.85f * 7f; + float num6 = 2.2f * 7f; + float num7 = 3.54f * 7f; + Pen pen = new Pen(Color.Black, 0.67f * 7f); + graphics.DrawEllipse(pen, new RectangleF(num3 - num5 + 1, num4 - num5 + 1, num5 * 2.12f, num5 * 2.12f)); + graphics.DrawEllipse(pen, new RectangleF(num3 - num6 + 0.5f, num4 - num6 + 0.5f, num6 * 2.12f, num6 * 2.12f)); + graphics.DrawEllipse(pen, new RectangleF(num3 - num7, num4 - num7, num7 * 2.12f, num7 * 2.12f)); + + Size symbolSize; + var section = Rectangle.Empty; + symbolSize = maxiBarcode.SymbolSize(graphics); + section.Width = symbolSize.Width - 12; + section.Height = symbolSize.Height - 12; + + var newBitmap = new Bitmap(section.Width, section.Height); + var newGraphics = Graphics.FromImage(newBitmap); + newGraphics.DrawImage(bitmap, 0, 0, section, GraphicsUnit.Pixel); + + byte[] data = null; + using (MemoryStream ms = new MemoryStream()) + { + newBitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + data = ms.ToArray(); + + this.DrawBarcode( + data, + maxiCode.PositionX, + maxiCode.PositionY, + section.Width, + section.Height, + true, + Label.FieldOrientation.Normal + ); + } + } + } + } + + private MaxicodeMode ConvertMaxiCodeMode(int mode) + { + return mode switch { + 2 => MaxicodeMode.Mode2, + 3 => MaxicodeMode.Mode3, + 4 => MaxicodeMode.Mode4, + 5 => MaxicodeMode.Mode5, + 6 => MaxicodeMode.Mode6, + _ => MaxicodeMode.Mode2, + }; + } + } +} diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/PDF417ElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/PDF417ElementDrawer.cs index c96348dc..1ecca2ec 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/PDF417ElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/PDF417ElementDrawer.cs @@ -56,30 +56,41 @@ 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.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(); - this.DrawBarcode(png, resizedImage.Height, resizedImage.Width, pdf417.FieldOrigin != null, x, y, 0, pdf417.FieldOrientation); + this.DrawBarcode(png, x, y, resizedImage.Width, resizedImage.Height, pdf417.FieldOrigin != null, pdf417.FieldOrientation); } } } @@ -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; diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/QrCodeElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/QrCodeElementDrawer.cs index 2fdb1316..701555f6 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/QrCodeElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/QrCodeElementDrawer.cs @@ -2,13 +2,19 @@ using BinaryKits.Zpl.Label.Elements; using SkiaSharp; using System.Collections.Generic; +using System.Text.RegularExpressions; using ZXing; using ZXing.QrCode; namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for QR Code Barcode elements + /// public class QrCodeElementDrawer : BarcodeDrawerBase { + private static readonly Regex gs1Regex = new Regex(@"^>;>8(.+)$", RegexOptions.Compiled); + /// public override bool CanDraw(ZplElementBase element) { @@ -22,22 +28,35 @@ public override void Draw(ZplElementBase element) { float x = qrcode.PositionX; float y = qrcode.PositionY; + + // support hand-rolled GS1 + bool gs1Mode = false; + var content = qrcode.Content; + + Match gs1Match = gs1Regex.Match(content); + if (gs1Match.Success) + { + content = gs1Match.Groups[1].Value; + gs1Mode = true; + } int verticalQuietZone = 10; var writer = new QRCodeWriter(); + // TODO: use QrCodeEncodingOptions in next version of ZXing.NET var hints = new Dictionary { { EncodeHintType.ERROR_CORRECTION, CovertErrorCorrection(qrcode.ErrorCorrectionLevel) }, { EncodeHintType.QR_MASK_PATTERN, qrcode.MaskValue }, { EncodeHintType.CHARACTER_SET, "UTF-8" }, - { EncodeHintType.MARGIN, 0 } + { EncodeHintType.MARGIN, 0 }, + { EncodeHintType.GS1_FORMAT, gs1Mode } }; - var result = writer.encode(qrcode.Content, BarcodeFormat.QR_CODE, 0, 0, hints); + var result = writer.encode(content, BarcodeFormat.QR_CODE, 0, 0, hints); using var resizedImage = this.BitMatrixToSKBitmap(result, qrcode.MagnificationFactor); var png = resizedImage.Encode(SKEncodedImageFormat.Png, 100).ToArray(); - this.DrawBarcode(png, resizedImage.Height + 2 * verticalQuietZone, resizedImage.Width, qrcode.FieldOrigin != null, x, y + verticalQuietZone, 0, qrcode.FieldOrientation); + this.DrawBarcode(png, x, y + verticalQuietZone, resizedImage.Width, resizedImage.Height + 2 * verticalQuietZone, qrcode.FieldOrigin != null, qrcode.FieldOrientation); } } @@ -52,5 +71,6 @@ private ZXing.QrCode.Internal.ErrorCorrectionLevel CovertErrorCorrection(ErrorCo _ => ZXing.QrCode.Internal.ErrorCorrectionLevel.M }; } + } } diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/RecallGraphicElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/RecallGraphicElementDrawer.cs index 4ec18641..8e025e2e 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/RecallGraphicElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/RecallGraphicElementDrawer.cs @@ -3,6 +3,9 @@ namespace BinaryKits.Zpl.Viewer.ElementDrawers { + /// + /// Drawer for Recall Graphic elements + /// public class RecallGraphicElementDrawer : ElementDrawerBase { /// diff --git a/src/BinaryKits.Zpl.Viewer/ElementDrawers/TextFieldElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ElementDrawers/TextFieldElementDrawer.cs index d709b32a..3659e855 100644 --- a/src/BinaryKits.Zpl.Viewer/ElementDrawers/TextFieldElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ElementDrawers/TextFieldElementDrawer.cs @@ -2,7 +2,6 @@ using BinaryKits.Zpl.Viewer.Helpers; using SkiaSharp; using SkiaSharp.HarfBuzz; -using System; namespace BinaryKits.Zpl.Viewer.ElementDrawers { @@ -24,12 +23,6 @@ public override bool IsReverseDraw(ZplElementBase element) return false; } - /// - public override void Draw(ZplElementBase element) - { - Draw(element, new DrawerOptions()); - } - /// public override void Draw(ZplElementBase element, DrawerOptions options) { @@ -50,7 +43,10 @@ public override void Draw(ZplElementBase element, DrawerOptions options) var typeface = options.FontLoader(font.FontName); var skFont = new SKFont(typeface, fontSize, scaleX); - using var skPaint = new SKPaint(skFont); + using var skPaint = new SKPaint(skFont) + { + IsAntialias = options.Antialias + }; string displayText = textField.Text; if (textField.UseHexadecimalIndicator) @@ -77,13 +73,13 @@ public override void Draw(ZplElementBase element, DrawerOptions options) switch (textField.Font.FieldOrientation) { case Label.FieldOrientation.Rotated90: - matrix = SKMatrix.CreateRotationDegrees(90, textField.PositionX + fontSize / 2, textField.PositionY + fontSize / 2); + matrix = SKMatrix.CreateRotationDegrees(90, x + fontSize / 2, y + fontSize / 2); break; case Label.FieldOrientation.Rotated180: - matrix = SKMatrix.CreateRotationDegrees(180, textField.PositionX + textBounds.Width / 2, textField.PositionY + fontSize / 2); + matrix = SKMatrix.CreateRotationDegrees(180, x + textBounds.Width / 2, y + fontSize / 2); break; case Label.FieldOrientation.Rotated270: - matrix = SKMatrix.CreateRotationDegrees(270, textField.PositionX + textBounds.Width / 2, textField.PositionY + textBounds.Width / 2); + matrix = SKMatrix.CreateRotationDegrees(270, x + textBounds.Width / 2, y + textBounds.Width / 2); break; case Label.FieldOrientation.Normal: break; @@ -94,13 +90,13 @@ public override void Draw(ZplElementBase element, DrawerOptions options) switch (textField.Font.FieldOrientation) { case Label.FieldOrientation.Rotated90: - matrix = SKMatrix.CreateRotationDegrees(90, textField.PositionX, textField.PositionY); + matrix = SKMatrix.CreateRotationDegrees(90, x, y); break; case Label.FieldOrientation.Rotated180: - matrix = SKMatrix.CreateRotationDegrees(180, textField.PositionX, textField.PositionY); + matrix = SKMatrix.CreateRotationDegrees(180, x, y); break; case Label.FieldOrientation.Rotated270: - matrix = SKMatrix.CreateRotationDegrees(270, textField.PositionX, textField.PositionY); + matrix = SKMatrix.CreateRotationDegrees(270, x, y); break; case Label.FieldOrientation.Normal: break; @@ -117,6 +113,11 @@ public override void Draw(ZplElementBase element, DrawerOptions options) y += textBoundBaseline.Height; } + if (textField.ReversePrint) + { + skPaint.BlendMode = SKBlendMode.Xor; + } + this._skCanvas.DrawShapedText(displayText, x, y, skPaint); } } diff --git a/src/BinaryKits.Zpl.Viewer/Helpers/FontHelper.cs b/src/BinaryKits.Zpl.Viewer/Helpers/FontHelper.cs deleted file mode 100644 index 8ead376e..00000000 --- a/src/BinaryKits.Zpl.Viewer/Helpers/FontHelper.cs +++ /dev/null @@ -1,28 +0,0 @@ -using SkiaSharp; -using System; -using System.Drawing; -using System.Drawing.Text; -using System.Runtime.InteropServices; - -namespace BinaryKits.Zpl.Viewer.Helpers -{ - public static class FontHelper - { - - public static Font ToSystemDrawingFont(this SKFont skFont) - { - using(var fontCollection = new PrivateFontCollection()) - using(var fontStream = skFont.Typeface.OpenStream()) - { - byte[] fontBytes = (byte[])Array.CreateInstance(typeof(byte), fontStream.Length); - fontStream.Read(fontBytes, fontBytes.Length); - IntPtr fontPtr = Marshal.AllocCoTaskMem(fontBytes.Length); - Marshal.Copy(fontBytes, 0, fontPtr, fontBytes.Length); - fontCollection.AddMemoryFont(fontPtr, fontBytes.Length); - Marshal.FreeCoTaskMem(fontPtr); - return new Font(fontCollection.Families[0], skFont.Size); - } - } - - } -} diff --git a/src/BinaryKits.Zpl.Viewer/Models/Code93BarcodeFieldData.cs b/src/BinaryKits.Zpl.Viewer/Models/Code93BarcodeFieldData.cs new file mode 100644 index 00000000..6eb7ee62 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/Models/Code93BarcodeFieldData.cs @@ -0,0 +1,13 @@ +using BinaryKits.Zpl.Label; + +namespace BinaryKits.Zpl.Viewer.Models +{ + internal class Code93BarcodeFieldData : FieldDataBase + { + public FieldOrientation FieldOrientation { get; set; } + public int Height { get; set; } + public bool PrintInterpretationLine { get; set; } + public bool PrintInterpretationLineAboveCode { get; set; } + public bool PrintCheckDigit { get; set; } + } +} diff --git a/src/BinaryKits.Zpl.Viewer/Models/MaxiCodeBarcodeFieldData.cs b/src/BinaryKits.Zpl.Viewer/Models/MaxiCodeBarcodeFieldData.cs new file mode 100644 index 00000000..2675180d --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/Models/MaxiCodeBarcodeFieldData.cs @@ -0,0 +1,12 @@ +using BinaryKits.Zpl.Label; + +namespace BinaryKits.Zpl.Viewer.Models +{ + public class MaxiCodeBarcodeFieldData : FieldDataBase + { + public int Mode { get; set; } + public int Position { get; set; } + public int Total { get; set; } + public bool UseHexadecimalIndicator { get; set; } + } +} diff --git a/src/BinaryKits.Zpl.Viewer/README.md b/src/BinaryKits.Zpl.Viewer/README.md index 767d2338..9d72a1be 100644 --- a/src/BinaryKits.Zpl.Viewer/README.md +++ b/src/BinaryKits.Zpl.Viewer/README.md @@ -21,4 +21,85 @@ foreach (var labelInfo in analyzeInfo.LabelInfos) var imageData = drawer.Draw(labelInfo.ZplElements); //imageData is bytes of png image } -``` \ No newline at end of file +``` + +# Adding Barcode support +Also applies to other "drawables", like shapes and graphics. + +## Common barcodes still missing support +### 1D +- [ ] Code 93: Supported by Xzing.net +- [ ] MSI: Supported by Xzing.net +- [ ] Codabar/NW7: Supported by Xzing.net +- [ ] Databar/RSS-14: Not supported by Xzing.net +- [ ] [Code 11](https://web.archive.org/web/20070202060711/http://www.barcodeisland.com/code11.phtml): Not supported by Xzing.net, simple to implement + +### 2D +- [ ] Maxicode: Not supported by Xzing.net, required for UPS labels +- [ ] Aztec: Supported by Xzing.net +- [ ] Micro pdf417: Not supported by Xzing.net + - ascii data only + - appears to be a subset of pdf417 +- [ ] Micro QrCode: Not supported by Xzing.net + - appears to be a subset of QrCode +- [ ] Composite: Not supported by Xzing.net, simple to implement + +## Examples +- [EAN Barcode](https://github.com/BinaryKits/BinaryKits.Zpl/commit/3fac409732e19be9e047ee71f942ba1f68c6fa5c) +- [Datamatrix Barcode](https://github.com/BinaryKits/BinaryKits.Zpl/commit/f79d01512eee7e3d16246e932877ca6d4aa4e306) +- [PDF417 Barcode](https://github.com/BinaryKits/BinaryKits.Zpl/pull/190/files) + +## Steps +Generally, looking at files in the directory for each step should give you a clue on how to implement a file. + +### 1: Create the abstract element +This is a class used to represent the barcode with all it's settings, mostly to be used for zpl generation. +We are currently in the middle of a (stalled) refactor which leads to some amount of code duplication. +A class should be created in these `/`: +- `BinaryKits.Zpl.Label/Elements/` +- `BinaryKits.Zpl.Protocol/Commands/` + +For 2d barcodes, implement `ZplPositionedElementBase` and `IFormatElement`, for 1d barcodes, subclass `ZplBarcode` + +### 2: Create a model in `BinaryKits.Zpl.Viewer/Models/` +This one is only used for the viewer project and should contain mostly the same properties as the class created in the previous step. + +### 3: Create the command analyzer in `BinaryKits.Zpl.Viewer/CommandAnalyzers/` +This is where you write the code for parsing zpl to the Model/Element. + +- Subclass from `ZplCommandAnalyzerBase` +- Use `this.SplitCommand` get all the zpl arguments +- Remember to check that the length of args array is long enough before trying to parse at an index +- Remember to set the defaults each parameter before parsing + - There are parameters stored in the virtual printer such as `^BY` + - Otherwise use the defaults as described in the zpl programming manual +- Because there is always data to encode, set the next field data encountered by the virtual printer to return the content to the model + +### 4: Add an entry to `BinaryKits.Zpl.Viewer/CommandAnalyzers/FieldDataZplCommandAnalyzer.cs` +This is to make sure that we grab the `text`, the model from the previous step it into a new instance of the abstract element. + +Find the massive `if` chain following `if (this.VirtualPrinter.NextElementFieldData != null)` and add an entry for your mode. + +Architecturally, this type of hard binding is probably not good. We should probably fix this. + +### 5: Add an entry to `BinaryKits.Zpl.Viewer/ZPLAnalyzer.cs` +Find the List `elementAnalyzers` and instantiate your analyzer there. + +### 6: Create a drawer in `BinaryKits.Zpl.Viewer/ElementDrawers` +- Subclass `BarcodeDrawerBase` +- Implement `CanDraw` by checking against the abstract element in step 1. +- Implement `Draw`. Working from the end: + - The end goal is to write some pixels (a skia bitmap) to the base canvas with `this.DrawBarcode` + - Those pixels will be obtained by converting an abstract barcode drawing to a png `SKBitmap` + - The abstract drawing will be generated by calling an external library with the correct parameters obtained from the abstract element + - Zxing.net uses hints to pass in extra parameters + - Some adaptative helper functions may needed to convert between the library types and skia bitmaps or binarykit.zpl models + - `BarcodeDrawerBase` may have some helper functions needed + +### 7: Add an entry to `BinaryKits.Zpl.Viewer/ZPLElementDrawer.cs` +Find the array `this._elementDrawers` and instantiate your drawer there. + +### 8: Create a test in to `BinaryKits.Zpl.Viewer.UnitTest/DrawerTest.cs` +- todo: load data from BinaryKits.Zpl.Viewer.UnitTest/Data/zpl like in the webapi project +- Use the `DefaultPrint` function when possible, you can pass dimensions, density and drawer options +- todo: an empty custom file `BinaryKits.Zpl.Viewer.UnitTest/Data/zpl/custom.zpl2` is not under vcs to enable quick testing diff --git a/src/BinaryKits.Zpl.Viewer/Symologies/ZplCode128Symbology.cs b/src/BinaryKits.Zpl.Viewer/Symologies/ZplCode128Symbology.cs new file mode 100644 index 00000000..a303a026 --- /dev/null +++ b/src/BinaryKits.Zpl.Viewer/Symologies/ZplCode128Symbology.cs @@ -0,0 +1,391 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace BinaryKits.Zpl.Viewer.Symologies +{ + public enum Code128CodeSet + { + Code128 = 0, + Code128A = 103, + Code128B = 104, + Code128C = 105 + } + + public class ZplCode128Symbology + { + // detect Type-C as late as possible + // ABC12345 -> START_B A B C 1 CODE_C 23 45 CHECK STOP + // and not -> START_B A B C CODE_C 12 34 CODE_B 5 CHECK STOP + private static readonly Regex swtichCRegex = new Regex(@"^\d\d(?:\d\d)+(?!\d)", RegexOptions.Compiled); + private static readonly Regex startCRegex = new Regex(@"^\d{4}", RegexOptions.Compiled); + private static readonly Regex digitPairRegex = new Regex(@"^\d\d", RegexOptions.Compiled); + + private static readonly Regex startCodeRegex = new Regex(@"^(>[9:;])(.+)$", RegexOptions.Compiled); + + private static readonly Dictionary invocationMap = new Dictionary() { + { "><", 62 }, + { ">0", 30 }, + { ">=", 94 }, + { ">1", 95 }, + { ">2", 96 }, + { ">3", 97 }, + { ">4", 98 }, + { ">5", 99 }, + { ">6", 100 }, + { ">7", 101 }, + { ">8", 102 } + }; + + private static readonly Dictionary startCodeMap = new Dictionary() { + { ">9", Code128CodeSet.Code128A }, + { ">:", Code128CodeSet.Code128B }, + { ">;", Code128CodeSet.Code128C } + }; + + private const string FNC_1 = "FNC_1"; + private const string FNC_2 = "FNC_2"; + private const string FNC_3 = "FNC_3"; + private const string FNC_4 = "FNC_4"; + + private const string SHIFT_A = "SHIFT_A"; + private const string SHIFT_B = "SHIFT_B"; + + private const string CODE_A = "CODE_A"; + private const string CODE_B = "CODE_B"; + private const string CODE_C = "CODE_C"; + + private const string START_A = "START_A"; + private const string START_B = "START_B"; + private const string START_C = "START_C"; + + private const string STOP = "STOP"; + + private static readonly int[] patterns; + private static readonly string[] codeAChars; + private static readonly string[] codeBChars; + private static readonly string[] codeCChars; + + private static readonly Dictionary codeAMap; + private static readonly Dictionary codeBMap; + private static readonly Dictionary codeCMap; + + private static readonly Dictionary)> codeMaps; + + /// + /// + /// + static ZplCode128Symbology() { + var codeSetTable = new[] + { + new { Value = 0, A = " ", B = " ", C = "00", Pattern = 0b11011001100 }, + new { Value = 1, A = "!", B = "!", C = "01", Pattern = 0b11001101100 }, + new { Value = 2, A = "\"", B = "\"", C = "02", Pattern = 0b11001100110 }, + new { Value = 3, A = "#", B = "#", C = "03", Pattern = 0b10010011000 }, + new { Value = 4, A = "$", B = "$", C = "04", Pattern = 0b10010001100 }, + new { Value = 5, A = "%", B = "%", C = "05", Pattern = 0b10001001100 }, + new { Value = 6, A = "&", B = "&", C = "06", Pattern = 0b10011001000 }, + new { Value = 7, A = "'", B = "'", C = "07", Pattern = 0b10011000100 }, + new { Value = 8, A = "(", B = "(", C = "08", Pattern = 0b10001100100 }, + new { Value = 9, A = ")", B = ")", C = "09", Pattern = 0b11001001000 }, + new { Value = 10, A = "*", B = "*", C = "10", Pattern = 0b11001000100 }, + new { Value = 11, A = "+", B = "+", C = "11", Pattern = 0b11000100100 }, + new { Value = 12, A = ",", B = ",", C = "12", Pattern = 0b10110011100 }, + new { Value = 13, A = "-", B = "-", C = "13", Pattern = 0b10011011100 }, + new { Value = 14, A = ".", B = ".", C = "14", Pattern = 0b10011001110 }, + new { Value = 15, A = "/", B = "/", C = "15", Pattern = 0b10111001100 }, + new { Value = 16, A = "0", B = "0", C = "16", Pattern = 0b10011101100 }, + new { Value = 17, A = "1", B = "1", C = "17", Pattern = 0b10011100110 }, + new { Value = 18, A = "2", B = "2", C = "18", Pattern = 0b11001110010 }, + new { Value = 19, A = "3", B = "3", C = "19", Pattern = 0b11001011100 }, + new { Value = 20, A = "4", B = "4", C = "20", Pattern = 0b11001001110 }, + new { Value = 21, A = "5", B = "5", C = "21", Pattern = 0b11011100100 }, + new { Value = 22, A = "6", B = "6", C = "22", Pattern = 0b11001110100 }, + new { Value = 23, A = "7", B = "7", C = "23", Pattern = 0b11101101110 }, + new { Value = 24, A = "8", B = "8", C = "24", Pattern = 0b11101001100 }, + new { Value = 25, A = "9", B = "9", C = "25", Pattern = 0b11100101100 }, + new { Value = 26, A = ":", B = ":", C = "26", Pattern = 0b11100100110 }, + new { Value = 27, A = ";", B = ";", C = "27", Pattern = 0b11101100100 }, + new { Value = 28, A = "<", B = "<", C = "28", Pattern = 0b11100110100 }, + new { Value = 29, A = "=", B = "=", C = "29", Pattern = 0b11100110010 }, + new { Value = 30, A = ">", B = ">", C = "30", Pattern = 0b11011011000 }, + new { Value = 31, A = "?", B = "?", C = "31", Pattern = 0b11011000110 }, + new { Value = 32, A = "@", B = "@", C = "32", Pattern = 0b11000110110 }, + new { Value = 33, A = "A", B = "A", C = "33", Pattern = 0b10100011000 }, + new { Value = 34, A = "B", B = "B", C = "34", Pattern = 0b10001011000 }, + new { Value = 35, A = "C", B = "C", C = "35", Pattern = 0b10001000110 }, + new { Value = 36, A = "D", B = "D", C = "36", Pattern = 0b10110001000 }, + new { Value = 37, A = "E", B = "E", C = "37", Pattern = 0b10001101000 }, + new { Value = 38, A = "F", B = "F", C = "38", Pattern = 0b10001100010 }, + new { Value = 39, A = "G", B = "G", C = "39", Pattern = 0b11010001000 }, + new { Value = 40, A = "H", B = "H", C = "40", Pattern = 0b11000101000 }, + new { Value = 41, A = "I", B = "I", C = "41", Pattern = 0b11000100010 }, + new { Value = 42, A = "J", B = "J", C = "42", Pattern = 0b10110111000 }, + new { Value = 43, A = "K", B = "K", C = "43", Pattern = 0b10110001110 }, + new { Value = 44, A = "L", B = "L", C = "44", Pattern = 0b10001101110 }, + new { Value = 45, A = "M", B = "M", C = "45", Pattern = 0b10111011000 }, + new { Value = 46, A = "N", B = "N", C = "46", Pattern = 0b10111000110 }, + new { Value = 47, A = "O", B = "O", C = "47", Pattern = 0b10001110110 }, + new { Value = 48, A = "P", B = "P", C = "48", Pattern = 0b11101110110 }, + new { Value = 49, A = "Q", B = "Q", C = "49", Pattern = 0b11010001110 }, + new { Value = 50, A = "R", B = "R", C = "50", Pattern = 0b11000101110 }, + new { Value = 51, A = "S", B = "S", C = "51", Pattern = 0b11011101000 }, + new { Value = 52, A = "T", B = "T", C = "52", Pattern = 0b11011100010 }, + new { Value = 53, A = "U", B = "U", C = "53", Pattern = 0b11011101110 }, + new { Value = 54, A = "V", B = "V", C = "54", Pattern = 0b11101011000 }, + new { Value = 55, A = "W", B = "W", C = "55", Pattern = 0b11101000110 }, + new { Value = 56, A = "X", B = "X", C = "56", Pattern = 0b11100010110 }, + new { Value = 57, A = "Y", B = "Y", C = "57", Pattern = 0b11101101000 }, + new { Value = 58, A = "Z", B = "Z", C = "58", Pattern = 0b11101100010 }, + new { Value = 59, A = "[", B = "[", C = "59", Pattern = 0b11100011010 }, + new { Value = 60, A = "\\", B = "\\", C = "60", Pattern = 0b11101111010 }, + new { Value = 61, A = "]", B = "]", C = "61", Pattern = 0b11001000010 }, + new { Value = 62, A = "^", B = "^", C = "62", Pattern = 0b11110001010 }, + new { Value = 63, A = "_", B = "_", C = "63", Pattern = 0b10100110000 }, + new { Value = 64, A = "\x00", B = "`", C = "64", Pattern = 0b10100001100 }, + new { Value = 65, A = "\x01", B = "a", C = "65", Pattern = 0b10010110000 }, + new { Value = 66, A = "\x02", B = "b", C = "66", Pattern = 0b10010000110 }, + new { Value = 67, A = "\x03", B = "c", C = "67", Pattern = 0b10000101100 }, + new { Value = 68, A = "\x04", B = "d", C = "68", Pattern = 0b10000100110 }, + new { Value = 69, A = "\x05", B = "e", C = "69", Pattern = 0b10110010000 }, + new { Value = 70, A = "\x06", B = "f", C = "70", Pattern = 0b10110000100 }, + new { Value = 71, A = "\x07", B = "g", C = "71", Pattern = 0b10011010000 }, + new { Value = 72, A = "\x08", B = "h", C = "72", Pattern = 0b10011000010 }, + new { Value = 73, A = "\x09", B = "i", C = "73", Pattern = 0b10000110100 }, + new { Value = 74, A = "\x0a", B = "j", C = "74", Pattern = 0b10000110010 }, + new { Value = 75, A = "\x0b", B = "k", C = "75", Pattern = 0b11000010010 }, + new { Value = 76, A = "\x0c", B = "l", C = "76", Pattern = 0b11001010000 }, + new { Value = 77, A = "\x0d", B = "m", C = "77", Pattern = 0b11110111010 }, + new { Value = 78, A = "\x0e", B = "n", C = "78", Pattern = 0b11000010100 }, + new { Value = 79, A = "\x0f", B = "o", C = "79", Pattern = 0b10001111010 }, + new { Value = 80, A = "\x10", B = "p", C = "80", Pattern = 0b10100111100 }, + new { Value = 81, A = "\x11", B = "q", C = "81", Pattern = 0b10010111100 }, + new { Value = 82, A = "\x12", B = "r", C = "82", Pattern = 0b10010011110 }, + new { Value = 83, A = "\x13", B = "s", C = "83", Pattern = 0b10111100100 }, + new { Value = 84, A = "\x14", B = "t", C = "84", Pattern = 0b10011110100 }, + new { Value = 85, A = "\x15", B = "u", C = "85", Pattern = 0b10011110010 }, + new { Value = 86, A = "\x16", B = "v", C = "86", Pattern = 0b11110100100 }, + new { Value = 87, A = "\x17", B = "w", C = "87", Pattern = 0b11110010100 }, + new { Value = 88, A = "\x18", B = "x", C = "88", Pattern = 0b11110010010 }, + new { Value = 89, A = "\x19", B = "y", C = "89", Pattern = 0b11011011110 }, + new { Value = 90, A = "\x1a", B = "z", C = "90", Pattern = 0b11011110110 }, + new { Value = 91, A = "\x1b", B = "{", C = "91", Pattern = 0b11110110110 }, + new { Value = 92, A = "\x1c", B = "|", C = "92", Pattern = 0b10101111000 }, + new { Value = 93, A = "\x1d", B = "}", C = "93", Pattern = 0b10100011110 }, + new { Value = 94, A = "\x1e", B = "~", C = "94", Pattern = 0b10001011110 }, + new { Value = 95, A = "\x1f", B = "\x7f", C = "95", Pattern = 0b10111101000 }, + new { Value = 96, A = FNC_3, B = FNC_3, C = "96", Pattern = 0b10111100010 }, + new { Value = 97, A = FNC_2, B = FNC_2, C = "97", Pattern = 0b11110101000 }, + new { Value = 98, A = SHIFT_B, B = SHIFT_A, C = "98", Pattern = 0b11110100010 }, + new { Value = 99, A = CODE_C, B = CODE_C, C = "99", Pattern = 0b10111011110 }, + new { Value = 100, A = CODE_B, B = FNC_4, C = CODE_B, Pattern = 0b10111101110 }, + new { Value = 101, A = FNC_4, B = CODE_A, C = CODE_A, Pattern = 0b11101011110 }, + new { Value = 102, A = FNC_1, B = FNC_1, C = FNC_1, Pattern = 0b11110101110 }, + new { Value = 103, A = START_A, B = START_A, C = START_A, Pattern = 0b11010000100 }, + new { Value = 104, A = START_B, B = START_B, C = START_B, Pattern = 0b11010010000 }, + new { Value = 105, A = START_C, B = START_C, C = START_C, Pattern = 0b11010011100 }, + new { Value = 106, A = STOP, B = STOP, C = STOP, Pattern = 0b1100011101011 }, + }; + + patterns = codeSetTable.Select(item => item.Pattern).ToArray(); + codeAChars = codeSetTable.Select(item => item.A).ToArray(); + codeBChars = codeSetTable.Select(item => item.B).ToArray(); + codeCChars = codeSetTable.Select(item => item.C).ToArray(); + + codeAMap = codeSetTable.ToDictionary(item => item.A, item => item.Value); + codeBMap = codeSetTable.ToDictionary(item => item.B, item => item.Value); + codeCMap = codeSetTable.ToDictionary(item => item.C, item => item.Value); + + codeMaps = new Dictionary)>() + { + { Code128CodeSet.Code128A, (codeAChars, codeAMap) }, + { Code128CodeSet.Code128B, (codeBChars, codeBMap) }, + { Code128CodeSet.Code128C, (codeCChars, codeCMap) }, + }; + } + + public static (List, string) Encode(string content, Code128CodeSet initialCodeSet, bool gs1) + { + List result = new List(); + var (data, interpretation) = Analyze(content, initialCodeSet); + + // TODO: magic constant FNC_1 + if (gs1 && data[1] != 102) + { + data.Insert(1, 102); + } + + foreach (int item in data) + { + result.AddRange(IntToBitArray(patterns[item])); + } + + int checksum = ComputeChecksum(data.ToArray()); + result.AddRange(IntToBitArray(patterns[checksum])); + // TODO: magic constant STOP + result.AddRange(IntToBitArray(patterns[106])); + + return (result, interpretation); + } + + private static int ComputeChecksum(int[] data) + { + int checksum = data[0]; + for (int i = 1; i < data.Length; i++) + { + checksum += i * data[i] % 103; + } + + return checksum % 103; + } + + private static IEnumerable IntToBitArray(int value) + { + Stack stack = new Stack(); + while (value > 0) + { + stack.Push((value & 1) == 1); + value >>= 1; + } + + return stack; + } + + private static (List, string) Analyze(string content, Code128CodeSet initialCodeSet) + { + List data = new(); + string interpretation = ""; + Code128CodeSet codeSet = initialCodeSet; + Match startCodeMatch = startCodeRegex.Match(content); + while (startCodeMatch.Success) + { + codeSet = startCodeMap[startCodeMatch.Groups[1].Value]; + content = startCodeMatch.Groups[2].Value; + startCodeMatch = startCodeRegex.Match(content); + } + + if (codeSet == Code128CodeSet.Code128) + { + return AnalyzeAuto(content); + } + + (var codeChars, var codeMap) = codeMaps[codeSet]; + + data.Add((int)codeSet); + for (int i = 0; i < content.Length; i++) + { + string symbol = content[i].ToString(); + if (symbol == ">" && i + 1 < content.Length) + { + i += 1; + symbol += content[i]; + if (invocationMap.ContainsKey(symbol)) + { + int value = invocationMap[symbol]; + data.Add(value); + string code = codeChars[value]; + if (code == CODE_A) + { + codeSet = Code128CodeSet.Code128A; + (codeChars, codeMap) = codeMaps[codeSet]; + } + else if (code == CODE_B) + { + codeSet = Code128CodeSet.Code128B; + (codeChars, codeMap) = codeMaps[codeSet]; + } + else if (code == CODE_C) + { + codeSet = Code128CodeSet.Code128C; + (codeChars, codeMap) = codeMaps[codeSet]; + } + } + else if (startCodeMap.ContainsKey(symbol)) + { + codeSet = startCodeMap[symbol]; + (codeChars, codeMap) = codeMaps[codeSet]; + } + else + { + throw new Exception($"Invalid invocation sequence in ZplCode128: {symbol}"); + } + } + else + { + if (codeSet == Code128CodeSet.Code128C) + { + if (i + 1 < content.Length) + { + i += 1; + symbol += content[i]; + } + else + { + codeSet = Code128CodeSet.Code128B; + (codeChars, codeMap) = codeMaps[codeSet]; + } + } + + if (!codeMap.ContainsKey(symbol)) { + throw new Exception($"Invalid symbol for {codeSet}: {symbol}"); + } + + data.Add(codeMap[symbol]); + interpretation += symbol; + } + } + + return (data, interpretation); + } + + private static (List, string) AnalyzeAuto(string content) + { + List data = new List(); + string interpretation = ""; + Code128CodeSet codeSet = Code128CodeSet.Code128B; + var codeMap = codeBMap; + if (startCRegex.IsMatch(content)) + { + codeSet = Code128CodeSet.Code128C; + codeMap = codeCMap; + } + data.Add((int)codeSet); + + while (content.Length > 0) + { + if (codeSet != Code128CodeSet.Code128C && swtichCRegex.IsMatch(content)) + { + data.Add(codeMap[CODE_C]); + codeSet = Code128CodeSet.Code128C; + codeMap = codeCMap; + } + else if (codeSet == Code128CodeSet.Code128C && !digitPairRegex.IsMatch(content)) + { + data.Add(codeMap[CODE_B]); + codeSet = Code128CodeSet.Code128B; + codeMap = codeBMap; + } + else + { + string symbol = content[0].ToString(); + if (codeSet == Code128CodeSet.Code128C) + { + symbol += content[1]; + content = content.Substring(2); + } + else + { + content = content.Substring(1); + } + + data.Add(codeMap[symbol]); + interpretation += symbol; + } + } + + return (data, interpretation); + } + + } +} diff --git a/src/BinaryKits.Zpl.Viewer/VirtualPrinter.cs b/src/BinaryKits.Zpl.Viewer/VirtualPrinter.cs index 0d60da28..2bfe8964 100644 --- a/src/BinaryKits.Zpl.Viewer/VirtualPrinter.cs +++ b/src/BinaryKits.Zpl.Viewer/VirtualPrinter.cs @@ -21,6 +21,7 @@ public class VirtualPrinter public FontInfo NextFont { get; private set; } public bool NextElementFieldReverse { get; private set; } + public bool NextElementFieldUseHexadecimalIndicator { get; private set; } public bool LabelReverse { get; private set; } public BarcodeInfo BarcodeInfo { get; private set; } @@ -82,6 +83,11 @@ public void SetNextElementFieldReverse() this.NextElementFieldReverse = true; } + public void SetNextElementFieldUseHexadecimalIndicator() + { + this.NextElementFieldUseHexadecimalIndicator = true; + } + public void SetLabelReverse(bool reverse) { this.LabelReverse = reverse; @@ -92,6 +98,11 @@ public void ClearNextElementFieldReverse() this.NextElementFieldReverse = false; } + public void ClearNextElementFieldUseHexadecimalIndicator() + { + this.NextElementFieldUseHexadecimalIndicator = false; + } + public void SetFontWidth(int fontWidth) { this.FontWidth = fontWidth; diff --git a/src/BinaryKits.Zpl.Viewer/ZplAnalyzer.cs b/src/BinaryKits.Zpl.Viewer/ZplAnalyzer.cs index 3fe6aa15..e816499d 100644 --- a/src/BinaryKits.Zpl.Viewer/ZplAnalyzer.cs +++ b/src/BinaryKits.Zpl.Viewer/ZplAnalyzer.cs @@ -12,6 +12,8 @@ namespace BinaryKits.Zpl.Viewer { public class ZplAnalyzer : IZplAnalyzer { + private static readonly Regex verticalWhitespaceRegex = new Regex(@"[\n\v\f\r]", RegexOptions.Compiled); + private readonly VirtualPrinter _virtualPrinter; private readonly IPrinterStorage _printerStorage; private readonly IFormatMerger _formatMerger; @@ -38,6 +40,7 @@ public AnalyzeInfo Analyze(string zplData) new BarCodeFieldDefaultZplCommandAnalyzer(this._virtualPrinter), new ChangeAlphanumericDefaultFontZplCommandAnalyzer(this._virtualPrinter), new Code39BarcodeZplCommandAnalyzer(this._virtualPrinter), + new Code93BarcodeZplCommandAnalyzer(this._virtualPrinter), new Code128BarcodeZplCommandAnalyzer(this._virtualPrinter), new CodeEAN13BarcodeZplCommandAnalyzer(this._virtualPrinter), new CommentZplCommandAnalyzer(this._virtualPrinter), @@ -48,6 +51,7 @@ public AnalyzeInfo Analyze(string zplData) new FieldBlockZplCommandAnalyzer(this._virtualPrinter), new FieldHexadecimalZplCommandAnalyzer(this._virtualPrinter), new FieldNumberCommandAnalyzer(this._virtualPrinter), + new FieldVariableZplCommandAnalyzer(this._virtualPrinter), new FieldReversePrintZplCommandAnalyzer(this._virtualPrinter), new LabelReversePrintZplCommandAnalyzer(this._virtualPrinter), new FieldSeparatorZplCommandAnalyzer(this._virtualPrinter, fieldDataAnalyzer), @@ -59,6 +63,7 @@ public AnalyzeInfo Analyze(string zplData) new Interleaved2of5BarcodeZplCommandAnalyzer(this._virtualPrinter), new ImageMoveZplCommandAnalyzer(this._virtualPrinter), new LabelHomeZplCommandAnalyzer(this._virtualPrinter), + new MaxiCodeBarcodeZplCommandAnalyzer(this._virtualPrinter), new QrCodeBarcodeZplCommandAnalyzer(this._virtualPrinter), new PDF417ZplCommandAnalyzer(this._virtualPrinter), new RecallFormatCommandAnalyzer(this._virtualPrinter), @@ -121,6 +126,11 @@ public AnalyzeInfo Analyze(string zplData) return analyzeInfo; } + // When adding new commands: 1 per line, always upper case, comment why if possible + private string[] ignoredCommands = { + "CI", // may be implemented in the future, but for now always set to CI128 + }; + private string[] SplitZplCommands(string zplData) { if (string.IsNullOrWhiteSpace(zplData)) @@ -128,11 +138,12 @@ private string[] SplitZplCommands(string zplData) return Array.Empty(); } - var cleanZpl = Regex.Replace(zplData, @"\r|\n", string.Empty); + var cleanZpl = verticalWhitespaceRegex.Replace(zplData, string.Empty); char caret = '^'; char tilde = '~'; List results = new(200); StringBuilder buffer = new(2000); + HashSet ignoredCommandsHS = new HashSet(ignoredCommands); for (int i = 0; i < cleanZpl.Length; i++) { char c = cleanZpl[i]; @@ -140,25 +151,33 @@ private string[] SplitZplCommands(string zplData) { string command = buffer.ToString(); buffer.Clear(); + + // all commands have at least 3 chars, even ^A because of required font parameter if (command.Length > 2) { PatchCommand(ref command, ref caret, ref tilde); - results.Add(command); - if (command.Substring(1, 2) == "CT") + + var commandLetters = command.Substring(1, 2).ToUpper(); + + if (commandLetters == "CT") { tilde = command[3]; - results.RemoveAt(results.Count - 1); } - else if (command.Substring(1, 2) == "CC") + else if (commandLetters == "CC") { caret = command[3]; - results.RemoveAt(results.Count - 1); + } + else if (!ignoredCommandsHS.Contains(commandLetters)) + { + results.Add(command); } } // likely invalid command - else if (command.Trim().Length > 0) { + else if (command.Trim().Length > 0) + { results.Add(command.Trim()); } + // no else case, multiple ^ or ~ in a row should not be valid commands to be processed } buffer.Append(c); } diff --git a/src/BinaryKits.Zpl.Viewer/ZplElementDrawer.cs b/src/BinaryKits.Zpl.Viewer/ZplElementDrawer.cs index 72bf4c23..a94c33b8 100644 --- a/src/BinaryKits.Zpl.Viewer/ZplElementDrawer.cs +++ b/src/BinaryKits.Zpl.Viewer/ZplElementDrawer.cs @@ -2,6 +2,8 @@ using BinaryKits.Zpl.Viewer.ElementDrawers; using SkiaSharp; using System; +using System.Collections.Generic; +using System.IO; using System.Linq; namespace BinaryKits.Zpl.Viewer @@ -24,6 +26,7 @@ public ZplElementDrawer(IPrinterStorage printerStorage, DrawerOptions drawerOpti { new Barcode128ElementDrawer(), new Barcode39ElementDrawer(), + new Barcode93ElementDrawer(), new BarcodeEAN13ElementDrawer(), new DataMatrixElementDrawer(), new FieldBlockElementDrawer(), @@ -32,6 +35,7 @@ public ZplElementDrawer(IPrinterStorage printerStorage, DrawerOptions drawerOpti new GraphicFieldElementDrawer(), new Interleaved2of5BarcodeDrawer(), new ImageMoveElementDrawer(), + new MaxiCodeElementDrawer(), new QrCodeElementDrawer(), new Pdf417ElementDrawer(), new RecallGraphicElementDrawer(), @@ -53,11 +57,65 @@ public byte[] Draw( double labelHeight = 152.4, int printDensityDpmm = 8) { + return this.DrawMulti(elements, labelWidth, labelHeight, printDensityDpmm)[0]; + } + + /// + /// Draw the label as PDF + /// + /// Zpl elements + /// Label width in millimeter + /// Label height in millimeter + /// Dots per millimeter + /// + public byte[] DrawPdf( + ZplElementBase[] elements, + double labelWidth = 101.6, + double labelHeight = 152.4, + int printDensityDpmm = 8) + { + return this.DrawMulti(elements, labelWidth, labelHeight, printDensityDpmm)[1]; + } + + /// + /// Draw the label on multiple canvases + /// + /// Zpl elements + /// Label width in millimeter + /// Label height in millimeter + /// Dots per millimeter + /// + public List DrawMulti( + ZplElementBase[] elements, + double labelWidth = 101.6, + double labelHeight = 152.4, + int printDensityDpmm = 8) + { + var result = new List(); + var imageHistory = new List(); var labelImageWidth = Convert.ToInt32(labelWidth * printDensityDpmm); var labelImageHeight = Convert.ToInt32(labelHeight * printDensityDpmm); - using var skBitmap = new SKBitmap(labelImageWidth, labelImageHeight); - using var skCanvas = new SKCanvas(skBitmap); + //use SKNWayCanvas to be able to draw on multiple canvases + using var skCanvas = new SKNWayCanvas(labelImageWidth, labelImageHeight); + + //add Bitmap canvas + var info = new SKImageInfo(labelImageWidth, labelImageHeight); + var surface = SKSurface.Create(info); + using var skImageCanvas = surface.Canvas; + skCanvas.AddCanvas(skImageCanvas); + + //add PDF canvas + // - When drawing PDF we need the Bitmap as well to fix inverted coloring + Stream pdfStream = new MemoryStream(); + using var document = SKDocument.CreatePdf(pdfStream); + using var pdfCanvas = document.BeginPage(labelImageWidth, labelImageHeight); + if (this._drawerOptions.PdfOutput == true) + { + skCanvas.AddCanvas(pdfCanvas); + } + + //make sure to have a transparent canvas for SKBlendMode.Xor to work properly skCanvas.Clear(SKColors.Transparent); foreach (var element in elements) @@ -70,16 +128,48 @@ public byte[] Draw( try { - if (drawer.IsReverseDraw(element)) + //The inverse drawing is moved to the element drawer, so only collect imageHistory for the PDF + if ((element is ZplFieldBlock + || element is ZplTextField + || element is ZplGraphicCircle + || element is ZplGraphicBox + ) + && drawer.IsReverseDraw(element) + && !drawer.IsWhiteDraw(element) + && !drawer.ForceBitmapDraw(element)) { - using var skBitmapInvert = new SKBitmap(skBitmap.Width, skBitmap.Height); + //save state before inverted draw + if (this._drawerOptions.PdfOutput == true) + { + imageHistory.Add(surface.Snapshot()); + } + } + else if (drawer.IsReverseDraw(element)) + { + //basically only ZplGraphicBox/Circle depending on requirements + using var skBitmapInvert = new SKBitmap(labelImageWidth, labelImageHeight); using var skCanvasInvert = new SKCanvas(skBitmapInvert); + skCanvasInvert.Clear(SKColors.Transparent); drawer.Prepare(this._printerStorage, skCanvasInvert); - drawer.Draw(element, _drawerOptions); - this.InvertDraw(skBitmap, skBitmapInvert); + //save state before inverted draw + if (this._drawerOptions.PdfOutput == true) + { + imageHistory.Add(surface.Snapshot()); + } + + //use color inversion on an reverse draw white element + if (drawer.IsWhiteDraw(element)) + { + this.InvertDrawWhite(skCanvas, skBitmapInvert); + } + else + { + this.InvertDraw(skCanvas, skBitmapInvert); + } + continue; } @@ -101,60 +191,116 @@ public byte[] Draw( } } - SKBitmap finalBitmap; + //check if we need to set a white background + var image = surface.Snapshot(); if (this._drawerOptions.OpaqueBackground == true) { - finalBitmap = new SKBitmap(labelImageWidth, labelImageHeight); - using (SKCanvas canvas = new SKCanvas(finalBitmap)) - { - SKPaint paint = new SKPaint - { - IsAntialias = true, - FilterQuality = SKFilterQuality.High - }; - canvas.Clear(SKColors.White); - canvas.DrawBitmap(skBitmap, 0, 0, paint); - } + using var surfaceWhiteBg = SKSurface.Create(info); + using var skImageCanvasWhiteBg = surfaceWhiteBg.Canvas; + skImageCanvasWhiteBg.Clear(SKColors.White); + + var surfaceImage = surface.Snapshot(); + var paint = new SKPaint(); + paint.BlendMode = SKBlendMode.SrcOver; + skImageCanvasWhiteBg.DrawImage(surfaceImage, 0f, 0f, paint); + + image = surfaceWhiteBg.Snapshot(); + } + + var imageData = image.Encode(_drawerOptions.RenderFormat, _drawerOptions.RenderQuality); + result.Add(imageData.ToArray()); + + //only return image + if (this._drawerOptions.PdfOutput == false) + { + result.Add(null); + return result; + } + + //Fix the PDF blend + this.FixPdfInvertDraw(info, imageHistory, surface, skCanvas); + + //close the PDF document + document.EndPage(); + document.Close(); + + //try to export the PDF stream to a byte array + if (pdfStream is MemoryStream memStream) + { + result.Add(memStream.ToArray()); } - else { - finalBitmap = skBitmap; + else + { + result.Add(null); } - using var data = finalBitmap.Encode(_drawerOptions.RenderFormat, _drawerOptions.RenderQuality); - return data.ToArray(); + return result; } - private void InvertDraw(SKBitmap skBitmap, SKBitmap skBitmapInvert) + /** + * PDF transparency and SKBlendMode are not very good friends, SKBlendMode.Xor behaves as SKBlendMode.SrcOver. + * + * This function extracts all the pixels that are removed in the draw process + * Then that is used to make a white image as overlay in the PDF to get the same effect as SKBlendMode.Xor + */ + private void FixPdfInvertDraw(SKImageInfo info, List imageHistory, SKSurface surface, SKCanvas skCanvas) { - // Fast local copy - var originalBytes = skBitmap.GetPixelSpan(); - var invertBytes = skBitmapInvert.GetPixelSpan(); + //fix inverted colors + using var surfacePdfInvertColorFix = SKSurface.Create(info); + using var skImageCanvasPdfInvertColorFix = surfacePdfInvertColorFix.Canvas; + skImageCanvasPdfInvertColorFix.Clear(SKColors.Transparent); - int total = originalBytes.Length / 4; - for (int i = 0; i < total; i++) + //make an image of everything that was once colored + foreach (var imageHistoryState in imageHistory) { - // RGBA8888 - int rLoc = (i << 2); - int gLoc = (i << 2) + 1; - int bLoc = (i << 2) + 2; - int aLoc = (i << 2) + 3; - if (invertBytes[aLoc] == 0) - { - continue; - } + var pdfPaint = new SKPaint(); + pdfPaint.BlendMode = SKBlendMode.SrcOver; + skImageCanvasPdfInvertColorFix.DrawImage(imageHistoryState, 0f, 0f, pdfPaint); + } - // Set color - byte rByte = (byte)(originalBytes[rLoc] ^ invertBytes[rLoc]); - byte gByte = (byte)(originalBytes[gLoc] ^ invertBytes[gLoc]); - byte bByte = (byte)(originalBytes[bLoc] ^ invertBytes[bLoc]); - byte aByte = (byte)(originalBytes[aLoc] ^ invertBytes[aLoc]); + //subtract the parts that are transparent in the final image + var finalSurfaceImage = surface.Snapshot(); + var pdfFinalPaint = new SKPaint(); + pdfFinalPaint.BlendMode = SKBlendMode.DstOut; + skImageCanvasPdfInvertColorFix.DrawImage(finalSurfaceImage, 0f, 0f, pdfFinalPaint); - var targetColor = new SKColor(rByte, gByte, bByte, aByte); + //now invert the colors of the pixels that should be white place it on the canvas + var pdfTransparentPartsImage = surfacePdfInvertColorFix.Snapshot(); + var pdfTransparentPartsBitmap = SKBitmap.FromImage(pdfTransparentPartsImage); + var pdfFinalPaintInverted = new SKPaint(); + var inverter = new float[20] { + -1f, 0f, 0f, 0f, 1f, + 0f, -1f, 0f, 0f, 1f, + 0f, 0f, -1f, 0f, 1f, + 0f, 0f, 0f, 1f, 0f + }; + pdfFinalPaintInverted.ColorFilter = SKColorFilter.CreateColorMatrix(inverter); + pdfFinalPaintInverted.BlendMode = SKBlendMode.SrcOver; + skCanvas.DrawBitmap(pdfTransparentPartsBitmap, 0, 0, pdfFinalPaintInverted); + } - int x, y; - y = Math.DivRem(i, skBitmapInvert.Width, out x); + private void InvertDraw(SKCanvas baseCanvas, SKBitmap bmToInvert) + { + using (SKPaint paint = new SKPaint()) + { + paint.BlendMode = SKBlendMode.Xor; + baseCanvas.DrawBitmap(bmToInvert, 0, 0, paint); + } + } - skBitmap.SetPixel(x, y, targetColor); + private void InvertDrawWhite(SKCanvas baseCanvas, SKBitmap bmToInvert) + { + using (SKPaint paint = new SKPaint()) + { + var inverter = new float[20] { + -1f, 0f, 0f, 0f, 1f, + 0f, -1f, 0f, 0f, 1f, + 0f, 0f, -1f, 0f, 1f, + 0f, 0f, 0f, 1f, 0f + }; + paint.ColorFilter = SKColorFilter.CreateColorMatrix(inverter); + paint.BlendMode = SKBlendMode.Xor; + baseCanvas.DrawBitmap(bmToInvert, 0, 0, paint); } } } diff --git a/src/Dockerfile b/src/Dockerfile index 00b5e999..03e874cb 100644 --- a/src/Dockerfile +++ b/src/Dockerfile @@ -8,6 +8,7 @@ RUN apt-get update \ libc6-dev \ libgdiplus \ libx11-dev \ + fonts-roboto \ && rm -rf /var/lib/apt/lists/* WORKDIR /app