├── .npmignore
├── ESC_POS.png
├── ESCPOS_Command_Manual.pdf
├── Dockerfile
├── docker-compose.yml
├── azure-pipelines.yml
├── ESCPOSTest
├── ESCPOSTest.csproj
├── StringDiffHelper.cs
└── UnitTest.cs
├── .devcontainer
├── devcontainer.json
└── Dockerfile
├── ESCPOS
├── LICENSE
├── ESCPOS.csproj
├── Utils
│ ├── Utils.cs
│ └── Enums.cs
├── Printer.cs
└── ESCPOSCommands
│ ├── SimpleCommands.cs
│ └── Commands.cs
├── ESCPOS.sln
├── CHANGELOG.md
├── .gitignore
└── README.md
/.npmignore:
--------------------------------------------------------------------------------
1 | README.md
2 | test-project
3 | .vscode
4 | .npmignore
5 |
--------------------------------------------------------------------------------
/ESC_POS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorocampos/ESCPOS/HEAD/ESC_POS.png
--------------------------------------------------------------------------------
/ESCPOS_Command_Manual.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/igorocampos/ESCPOS/HEAD/ESCPOS_Command_Manual.pdf
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
2 | WORKDIR /app
3 | COPY . .
4 | WORKDIR /app
5 | RUN dotnet publish ./ESCPOS/ESCPOS.csproj -c Release
6 | VOLUME ["/output"]]
7 | COPY ./ESCPOS/bin/Debug/netstandard2.0 /output
8 | #RUN ["dotnet","test"]
9 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | escpos:
4 | image: escpos
5 | container_name: escpos_compose
6 | build:
7 | context: .
8 | dockerfile: Dockerfile
9 | volumes:
10 | - /output:/app/ESCPOS/bin/Debug/netstandard2.0/
11 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | trigger:
2 | - master
3 |
4 | pool:
5 | vmImage: 'ubuntu-latest'
6 |
7 | variables:
8 | buildConfiguration: 'Release'
9 |
10 | steps:
11 | - task: DotNetCoreCLI@2
12 | displayName: Build
13 | inputs:
14 | command: build
15 | projects: '**/*.csproj'
16 | - task: DotNetCoreCLI@2
17 | inputs:
18 | command: test
19 | projects: '**/*Test/*.csproj'
20 | arguments: '--configuration $(buildConfiguration)'
21 |
--------------------------------------------------------------------------------
/ESCPOSTest/ESCPOSTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | true
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Azure Functions & C# - .NET 6 (Isolated)",
3 | "dockerFile": "Dockerfile",
4 | "forwardPorts": [ 7071 ],
5 |
6 | // Configure tool-specific properties.
7 | "customizations": {
8 | // Configure properties specific to VS Code.
9 | "vscode": {
10 | // Add the IDs of extensions you want installed when the container is created.
11 | "extensions": [
12 | "ms-dotnettools.csharp"
13 | ]
14 | }
15 | },
16 |
17 | // Use 'postCreateCommand' to run commands after the container is created.
18 | // "postCreateCommand": "dotnet restore",
19 |
20 | // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
21 | "remoteUser": "vscode",
22 | "features": {
23 | "ghcr.io/devcontainers/features/dotnet:1": {}
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | # Find the Dockerfile at this URL
2 | # https://github.com/Azure/azure-functions-docker/blob/dev/host/4/bullseye/amd64/dotnet/dotnet-isolated/dotnet-isolated-core-tools.Dockerfile
3 | FROM mcr.microsoft.com/azure-functions/dotnet-isolated:4-dotnet-isolated6.0-core-tools
4 |
5 | # Uncomment following lines If you want to enable Development Container Script
6 | # For more details https://github.com/microsoft/vscode-dev-containers/tree/main/script-library
7 |
8 | # Avoid warnings by switching to noninteractive
9 | # ENV DEBIAN_FRONTEND=noninteractive
10 |
11 | # # Comment out these lines if you want to use zsh.
12 |
13 | # ARG INSTALL_ZSH=true
14 | # ARG USERNAME=vscode
15 | # ARG USER_UID=1000
16 | # ARG USER_GID=$USER_UID
17 |
18 | # RUN apt-get update && curl -ssL https://raw.githubusercontent.com/microsoft/vscode-dev-containers/main/script-library/common-debian.sh -o /tmp/common-script.sh \
19 | # && /bin/bash /tmp/common-script.sh "$INSTALL_ZSH" "$USERNAME" "$USER_UID" "$USER_GID" \
20 | # && rm /tmp/common-script.sh
--------------------------------------------------------------------------------
/ESCPOS/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Igor
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ESCPOS.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29215.179
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ESCPOS", "ESCPOS\ESCPOS.csproj", "{69B133DE-222B-4061-BC13-CE345CF176FD}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ESCPOSTest", "ESCPOSTest\ESCPOSTest.csproj", "{A4759F20-0E7D-41A5-B0C0-239FED8D9383}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {69B133DE-222B-4061-BC13-CE345CF176FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {69B133DE-222B-4061-BC13-CE345CF176FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {69B133DE-222B-4061-BC13-CE345CF176FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {69B133DE-222B-4061-BC13-CE345CF176FD}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {A4759F20-0E7D-41A5-B0C0-239FED8D9383}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {A4759F20-0E7D-41A5-B0C0-239FED8D9383}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {A4759F20-0E7D-41A5-B0C0-239FED8D9383}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {A4759F20-0E7D-41A5-B0C0-239FED8D9383}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {0ADE0FC4-C99F-477D-B34E-5B3EBA62DAA1}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/ESCPOS/ESCPOS.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | Igor Campos
6 | A .NET Standard 2.0 ESC/POS Printer Commands Helper
7 | https://github.com/igorocampos/ESCPOS
8 | https://github.com/igorocampos/ESCPOS
9 | true
10 |
11 | true
12 | LICENSE
13 | 1.3.0.0
14 | 1.3.0.0
15 | 1.3.0
16 | ESC_POS.png
17 | http://campos.cf/ESC_POS.png
18 | escpos;esc;pos;receipt;thermal;printer;esc/pos;sat;cfe
19 | true
20 | true
21 | true
22 | snupkg
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | True
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # v1.3.0
2 | - Methods PrintBarcode and PrintQRCode are obsolete and will thow error if used (see #29)
3 | - Updated Tests to .NET 8
4 | - Printer class added, so things like Column and Encoding can be set and leveraged
5 |
6 | # v1.2.4
7 | - Renamed methods PrintBarcode and PrintQRCode and marked old ones as obsolete (see #29)
8 | - Added support for any Encoding instead of just UTF-8
9 | - Added Simple Commands as alias of existing ones (see #30)
10 | - Created Extension Methods for Barcode and QRCode (see #31)
11 |
12 | # v1.2.3
13 | - Added new optional parameter for Barcode command (see [#26](https://github.com/igorocampos/ESCPOS/issues/26)).
14 | - Added support for GitHub Codespaces
15 | - Added Dockerfile and docker-compose to the project
16 | - Updated Tests to .NET 6
17 |
18 | # v1.2.2
19 | - Fixed SourceLink.
20 | - Added new unit tests for extension method `Add`.
21 |
22 | # v1.2.1
23 | - Enabled SourceLink.
24 |
25 | # v1.2.0
26 | - New Utility overload for extension method `Add`, accepting string parameters instead of byte arrays. This will prevent a lot of `.ToBytes()` in the code.
27 | - New `SelectCharSize` method that combines Width and Height size of characters (see [#10](https://github.com/igorocampos/ESCPOS/issues/10)).
28 |
29 | # v1.1.2
30 | - Bug fixed in `PrintBarCode` method when using CODE128 barcodes (see [#9](https://github.com/igorocampos/ESCPOS/issues/9)).
31 |
32 | # v1.1.1
33 | - `Print` method now allows a network address (host:port) as a printer address.
34 |
35 | # v1.1.0
36 | - Moved `Print` method to `Commands.cs`.
37 | - Moved all enums to `Enums.cs`.
38 |
39 | # v1.0.3
40 | - No relevant changed.
41 | - `AssemblyVersion` was corrected.
42 |
43 | # v1.0.2
44 | - Renamed `QRCodeCorrection` enum.
45 | - Created `Add` extension method for multiple strings as parameters.
46 | - `PrintQRCode` method now has default values in all parameters but content.
47 | - Created `Print` extension method for byte array that sends data to a printer address.
48 | - Created `ToBytes` extension method to convert UTF-8 strings into a byte array.
49 | - Add Unit Tests project.
50 |
51 | # v1.0.1
52 | - First functional version.
53 | - No Test Project is available yet.
54 |
--------------------------------------------------------------------------------
/ESCPOS/Utils/Utils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 |
4 | namespace ESCPOS.Utils
5 | {
6 | public static class Utils
7 | {
8 | private static byte[] Add(this byte[] array1, byte[] array2)
9 | {
10 | if (array1 is null)
11 | return array2;
12 |
13 | if (array2 is null)
14 | return array1;
15 |
16 | byte[] result = new byte[array1.Length + array2.Length];
17 | Array.Copy(array1, result, array1.Length);
18 | Array.Copy(array2, 0, result, array1.Length, array2.Length);
19 | return result;
20 | }
21 |
22 | public static byte[] Add(this byte[] array1, params object[] objects)
23 | {
24 | foreach (var obj in objects)
25 | {
26 | if (obj is byte[] array2)
27 | array1 = array1.Add(array2);
28 | else if (obj is string str)
29 | array1 = array1.Add(str.ToBytes());
30 | }
31 | return array1;
32 | }
33 |
34 | public static byte[] Add(this byte[] array1, Encoding encoding, params object[] objects)
35 | {
36 | foreach (var obj in objects)
37 | {
38 | if (obj is byte[] array2)
39 | array1 = array1.Add(array2);
40 | else if (obj is string str)
41 | array1 = array1.Add(str.ToBytes(encoding));
42 | }
43 | return array1;
44 | }
45 |
46 |
47 | public static byte[] Add(this byte[] array, params string[] strings)
48 | {
49 | StringBuilder sb = new StringBuilder();
50 | foreach (string str in strings)
51 | sb.Append(str);
52 |
53 | return array.Add(sb.ToString().ToBytes());
54 | }
55 |
56 | public static byte[] Add(this byte[] array, Encoding encoding, params string[] strings)
57 | {
58 | StringBuilder sb = new StringBuilder();
59 | foreach (string str in strings)
60 | sb.Append(str);
61 |
62 | return array.Add(sb.ToString().ToBytes(encoding));
63 | }
64 |
65 | public static byte[] Add(this byte[] array1, params byte[][] arrays)
66 | {
67 | foreach (byte[] array2 in arrays)
68 | array1 = array1.Add(array2);
69 |
70 | return array1;
71 | }
72 |
73 | public static byte[] ToBytes(this string source, Encoding encoding = null)
74 | => (encoding ?? Encoding.UTF8).GetBytes(source);
75 |
76 | public static string ToUTF8(this byte[] array)
77 | => Encoding.UTF8.GetString(array);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/ESCPOSTest/StringDiffHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.TestTools.UnitTesting;
2 | using System;
3 | using System.Globalization;
4 | using System.IO;
5 |
6 | namespace ESCPOSTest
7 | {
8 | //For more information, please see https://haacked.com/archive/2012/01/14/comparing-strings-in-unit-tests.aspx/
9 | public static class StringDiffHelper
10 | {
11 | public static void ShouldEqualWithDiff(string actualValue, string expectedValue, DiffStyle diffStyle = DiffStyle.Full)
12 | => ShouldEqualWithDiff(actualValue, expectedValue, diffStyle, Console.Out);
13 |
14 | public static void ShouldEqualWithDiff(string actualValue, string expectedValue, DiffStyle diffStyle, TextWriter output)
15 | {
16 | if (actualValue is null || expectedValue is null)
17 | {
18 | Assert.AreEqual(expectedValue, actualValue);
19 | return;
20 | }
21 |
22 | if (actualValue.Equals(expectedValue, StringComparison.Ordinal))
23 | return;
24 |
25 | output.WriteLine(" Idx Expected Actual");
26 | output.WriteLine("-------------------------");
27 | int maxLen = Math.Max(actualValue.Length, expectedValue.Length);
28 | int minLen = Math.Min(actualValue.Length, expectedValue.Length);
29 | for (int i = 0; i < maxLen; i++)
30 | if (diffStyle != DiffStyle.Minimal || i >= minLen || actualValue[i] != expectedValue[i])
31 | // put a mark beside a differing row index character decimal value character safe string character decimal value character safe string
32 | output.WriteLine($"{(i < minLen && actualValue[i] == expectedValue[i] ? " " : " * ")} {i,-3} {(i < expectedValue.Length ? ((int)expectedValue[i]).ToString() : ""),-4} {(i < expectedValue.Length ? expectedValue[i].ToSafeString() : ""),-3} {(i < actualValue.Length ? ((int)actualValue[i]).ToString() : ""),-4} {(i < actualValue.Length ? actualValue[i].ToSafeString() : ""),-3}");
33 |
34 | output.WriteLine();
35 | Assert.AreEqual(expectedValue, actualValue);
36 | }
37 |
38 | private static string ToSafeString(this char c)
39 | {
40 | if (char.IsControl(c) || char.IsWhiteSpace(c))
41 | switch (c)
42 | {
43 | case '\r': return @"\r";
44 | case '\n': return @"\n";
45 | case '\t': return @"\t";
46 | case '\a': return @"\a";
47 | case '\v': return @"\v";
48 | case '\f': return @"\f";
49 | default: return $"\\u{(int)c:X};";
50 | }
51 |
52 | return c.ToString(CultureInfo.InvariantCulture);
53 | }
54 | }
55 |
56 | public enum DiffStyle
57 | {
58 | Full,
59 | Minimal
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ESCPOS/Printer.cs:
--------------------------------------------------------------------------------
1 | using ESCPOS.Utils;
2 | using System;
3 | using System.Text;
4 |
5 | namespace ESCPOS
6 | {
7 | public class Printer
8 | {
9 | public Encoding Encoding { get; set; }
10 | public int Columns { get; set; } = 42;
11 | public string Address { get; set; }
12 | public byte[] CommandCache { get; set; }
13 |
14 | public byte[] HorizontalLine => "".PadLeft(Columns, '-').ToBytes(Encoding);
15 |
16 | public byte[] HorizontalDoubleLine => "".PadLeft(Columns, '=').ToBytes(Encoding);
17 |
18 | ///
19 | /// Based on printer's column count provides a byte array for 1 full line where both texts fit, first one aligned to the left and the other to the right.
20 | /// If total length of both texts is greater than printer's column count, Left Aligned text will get truncated to fit both and a whitespace will be added in between
21 | /// Printer will have Text Alignment set to the Left afterwards
22 | ///
23 | /// Text that will be aligned to the Left
24 | /// Text that will be aligned to the Right
25 | /// Byte array that contains a full line with one text aligned to the left and the other to the right
26 | public byte[] SameLineLeftAndRightAlignedText(string leftAlignedText, string rightAlignedText)
27 | {
28 | int totalLength = leftAlignedText.Length + rightAlignedText.Length;
29 | if (rightAlignedText.Length > Columns)
30 | throw new ArgumentException($"Length of Right-Aligned text ({rightAlignedText.Length}) surpass the printer's column count ({Columns}).");
31 |
32 | if (rightAlignedText.Length >= Columns -1)
33 | return Commands.AlignToRight.Add(rightAlignedText.ToBytes(Encoding), Commands.AlignToLeft);
34 |
35 | string result;
36 | if (totalLength >= Columns)
37 | result = $"{leftAlignedText.Substring(0, Columns - (rightAlignedText.Length + 1))} {rightAlignedText}";
38 | else
39 | {
40 | int padCount = Columns - totalLength;
41 | result = $"{leftAlignedText}{rightAlignedText.PadLeft(padCount + rightAlignedText.Length, ' ')}";
42 | }
43 | return result.ToBytes(Encoding).Add(Commands.AlignToLeft);
44 | }
45 |
46 | ///
47 | /// Clears the CommandCache for this printer instance.
48 | ///
49 | public void ClearCache()
50 | => CommandCache = null;
51 |
52 | public void AddToCache(params object[] objects)
53 | => CommandCache = CommandCache.Add(Encoding, objects);
54 |
55 | ///
56 | /// Sends content of CommandCache to the set Address for this printer. Once that's done CommandCache will be cleared.
57 | /// Printer Address can't be null empty, or in an unexpected format.
58 | ///
59 | ///
60 | /// is null, empty, or in an unexpected format.
61 | public void Print()
62 | {
63 | if (CommandCache is null)
64 | return;
65 |
66 | try
67 | {
68 | CommandCache.Print(Address);
69 | }
70 | catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException)
71 | {
72 | throw new InvalidOperationException("Printer Address can't be null empty, or in an unexpected format.", ex);
73 | }
74 | ClearCache();
75 | }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ESCPOS/Utils/Enums.cs:
--------------------------------------------------------------------------------
1 | namespace ESCPOS
2 | {
3 | public enum OnOff
4 | {
5 | Off,
6 | On
7 | }
8 |
9 | public enum QRCodeModel
10 | {
11 | Model1 = 49,
12 | Model2
13 | }
14 |
15 | public enum QRCodeCorrection
16 | {
17 | Percent7 = 48,
18 | Percent15,
19 | Percent25,
20 | Percent30
21 | }
22 |
23 | public enum QRCodeSize
24 | {
25 | Tiny = 2,
26 | Small,
27 | Normal,
28 | Large
29 | }
30 |
31 | public enum Justification
32 | {
33 | Left,
34 | Center,
35 | Right
36 | }
37 |
38 | public enum BarCodeType
39 | {
40 | UPC_A = 65,
41 | UPC_E,
42 | EAN13,
43 | EAN8,
44 | CODE39,
45 | ITF,
46 | CODABAR,
47 | CODE93,
48 | CODE128
49 | }
50 |
51 | public enum HRIPosition
52 | {
53 | NotPrinted,
54 | AboveBarcode,
55 | BelowBarcode,
56 | BothAboveAndBelow
57 | }
58 |
59 | public enum CharSizeHeight
60 | {
61 | Normal,
62 | Double,
63 | Triple,
64 | Quadruple,
65 | Quintuple,
66 | Sextuple,
67 | Septuple,
68 | Octuple
69 | }
70 |
71 | #pragma warning disable CA1027 // Mark enums with FlagsAttribute
72 | public enum CharSizeWidth
73 | #pragma warning restore CA1027 // Mark enums with FlagsAttribute
74 | {
75 | Normal,
76 | Double = 16,
77 | Triple = 32,
78 | Quadruple = 48,
79 | Quintuple = 64,
80 | Sextuple = 80,
81 | Septuple = 96,
82 | Octuple = 112
83 | }
84 |
85 | public enum ClockwiseDirection
86 | {
87 | Counterclockwise,
88 | Clockwise
89 | }
90 |
91 | public enum Direction
92 | {
93 | LeftToRight,
94 | BottomToTop,
95 | RightToLeft,
96 | TopToBottom
97 | }
98 |
99 | public enum CharSet
100 | {
101 | USA,
102 | France,
103 | Germany,
104 | UK,
105 | DenmarkI,
106 | Sweden,
107 | Italy,
108 | SpainI,
109 | Japan,
110 | Norway,
111 | DenmarkII,
112 | SpainII,
113 | LatinAmerica
114 | }
115 |
116 | public enum CodeTable
117 | {
118 | USA,
119 | Katakana,
120 | Multilingual,
121 | Portuguese,
122 | CanadianFrench,
123 | Nordic,
124 | Windows1252 = 16,
125 | Cyrillic,
126 | Latin2,
127 | OEM858,
128 | SpacePage = 255
129 | }
130 |
131 | public enum Font
132 | {
133 | A,
134 | B
135 | }
136 |
137 | #pragma warning disable CA1027 // Mark enums with FlagsAttribute
138 | public enum PrintMode
139 | #pragma warning restore CA1027 // Mark enums with FlagsAttribute
140 | {
141 | Reset,
142 | FontB,
143 | EmphasizedOn = 8,
144 | DoubleHeight = 16,
145 | DoubleWidth = 32,
146 | UnderlineOn = 128
147 | }
148 |
149 | public enum UnderlineMode
150 | {
151 | Off,
152 | OneDotThick,
153 | TwoDotsThick
154 | }
155 |
156 | #pragma warning disable CA1027 // Mark enums with FlagsAttribute
157 | public enum LineSpacing
158 | #pragma warning restore CA1027 // Mark enums with FlagsAttribute
159 | {
160 | Default = 2,
161 | Double = 4,
162 | Triple = 6
163 | }
164 |
165 | public enum BarcodeWidth
166 | {
167 | VeryThin = 1,
168 | Thin,
169 | Normal,
170 | Thick,
171 | VeryThick,
172 | Thickest
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/ESCPOS/ESCPOSCommands/SimpleCommands.cs:
--------------------------------------------------------------------------------
1 | namespace ESCPOS
2 | {
3 | public static partial class Commands
4 | {
5 | ///
6 | /// Moves the print position to the next tab position.
7 | ///
8 | ///
9 | /// ·This command is ignored unless the next tab position has been set.
10 | /// ·If the next horizontal tab position exceeds the printing area, the printer sets the printing position to[Printing area width + 1].
11 | /// ·If this command is received when the printing position is at [printing area width +1], the printer executes print buffer-full printing of the current line and horizontal tab processing from the beginning of the next line.
12 | /// ·The default setting of the horizontal tab position for the paper roll is font A (12 x 24) every 8th character(9th, 17th, 25th, … column).
13 | ///
14 | public static byte[] HorizontalTab => new byte[] { 0x09 };
15 |
16 | ///
17 | /// Prints the data in the print buffer and feeds one line based on the current line spacing.
18 | ///
19 | ///
20 | /// ·This command sets the print position to the beginning of the line.
21 | ///
22 | public static byte[] LineFeed => new byte[] { 0x0A };
23 |
24 | ///
25 | /// When automatic line feed is enabled, this command functions the same as LF, when automatic line feed is disabled, this command is ignored
26 | ///
27 | ///
28 | /// ·Sets the print starting position to the beginning of the line.
29 | /// ·The automatic line feed is ignored.
30 | ///
31 | public static byte[] CarriageReturn => new byte[] { 0x0D };
32 |
33 | ///
34 | /// Prints the data in the print buffer and returns to standard mode.
35 | ///
36 | ///
37 | /// ·The buffer data is deleted after being printed.
38 | /// ·The printer does not execute paper cutting.
39 | /// ·This command sets the print position to the beginning of the line.
40 | /// ·This command is enabled only in page mode.
41 | ///
42 | public static byte[] PrintAndReturnToStandardMode => new byte[] { 0x0C };
43 |
44 | ///
45 | /// In page mode, delete all the print data in the current printable area.
46 | ///
47 | ///
48 | /// ·This command is enabled only in page mode.
49 | /// ·If data that existed in the previously specified printable area also exists in the currently specified printable area, it is deleted.
50 | ///
51 | public static byte[] CancelPrint => new byte[] { 0x18 };
52 |
53 | ///
54 | /// ESC @
55 | ///
56 | public static byte[] InitializePrinter => new byte[] { 0x1B, 0x40 };
57 |
58 | ///
59 | /// ESC p m t1 t2
60 | ///
61 | public static byte[] OpenDrawer => new byte[] { 0x1B, 0x70, 0x00, 0x3C, 0x78 };
62 |
63 | ///
64 | /// ESC m
65 | ///
66 | public static byte[] PaperCut => new byte[] { 0x1B, 0x6D };
67 |
68 | ///
69 | /// ESC i
70 | ///
71 | public static byte[] FullPaperCut => new byte[] { 0x1B, 0x69 };
72 |
73 | //Alias
74 | public static byte[] HT => HorizontalTab;
75 | public static byte[] LF => LineFeed;
76 | public static byte[] CR => CarriageReturn;
77 | public static byte[] FF => PrintAndReturnToStandardMode;
78 | public static byte[] CAN => CancelPrint;
79 |
80 | public static byte[] PrintModeReset => SelectPrintMode(PrintMode.Reset);
81 | public static byte[] PrintModeFontB => SelectPrintMode(PrintMode.FontB);
82 | public static byte[] PrintModeEmphasis => SelectPrintMode(PrintMode.EmphasizedOn);
83 |
84 | public static byte[] UnderlineModeOff => SelectUnderlineMode(UnderlineMode.Off);
85 | public static byte[] UnderlineModeOn => SelectUnderlineMode(UnderlineMode.OneDotThick);
86 |
87 | public static byte[] DoubleStrikeOn => DoubleStrike(OnOff.On);
88 | public static byte[] DoubleStrikeOff => DoubleStrike(OnOff.Off);
89 |
90 | public static byte[] UseFontA => SelectCharacterFont(Font.A);
91 | public static byte[] UseFontB => SelectCharacterFont(Font.B);
92 |
93 | public static byte[] AlignToLeft => SelectJustification(Justification.Left);
94 | public static byte[] AlignToRight => SelectJustification(Justification.Right);
95 | public static byte[] AlignToCenter => SelectJustification(Justification.Center);
96 |
97 | public static byte[] CharSizeDoubleHeight => SelectCharSize(CharSizeWidth.Normal, CharSizeHeight.Double);
98 | public static byte[] CharSizeDoubleWidth => SelectCharSize(CharSizeWidth.Double, CharSizeHeight.Normal);
99 | public static byte[] CharSizeDoubleHeightAndWidth => SelectCharSize(CharSizeWidth.Double, CharSizeHeight.Double);
100 | public static byte[] CharSizeReset => SelectCharSize(CharSizeWidth.Normal, CharSizeHeight.Normal);
101 |
102 | public static byte[] HRIAboveBarcode => SelectHRIPosition(HRIPosition.AboveBarcode);
103 | public static byte[] HRIBelowBarcode => SelectHRIPosition(HRIPosition.BelowBarcode);
104 | public static byte[] HRINotPrinted => SelectHRIPosition(HRIPosition.NotPrinted);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/ESCPOSTest/UnitTest.cs:
--------------------------------------------------------------------------------
1 | using ESCPOS;
2 | using ESCPOS.Utils;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using System;
5 | using System.IO;
6 | using System.Text;
7 | using static ESCPOS.Commands;
8 | using static ESCPOSTest.StringDiffHelper;
9 |
10 | namespace ESCPOSTest
11 | {
12 | [TestClass]
13 | public class UnitTest
14 | {
15 | private const string TEST_FILE = "test.txt";
16 | private const string TEXT_DATA = "Data test with some special characters: $ñáãç*/@\"'^{}";
17 |
18 | [TestMethod]
19 | public void Add_TwoByteArrays_FirstNull()
20 | {
21 | byte[] array1 = null;
22 | array1 = array1.Add(LF);
23 | ShouldEqualWithDiff(array1.ToUTF8(), LF.ToUTF8());
24 | }
25 |
26 | [TestMethod]
27 | public void Add_TwoByteArrays_SecondNull()
28 | {
29 | byte[] array2 = null;
30 | array2 = LF.Add(array2);
31 | ShouldEqualWithDiff(array2.ToUTF8(), LF.ToUTF8());
32 | }
33 |
34 | [TestMethod]
35 | public void Add_TwoByteArrays()
36 | {
37 | byte[] array = LF.Add(HT);
38 | ShouldEqualWithDiff(array.ToUTF8(), $"{LF.ToUTF8()}{HT.ToUTF8()}");
39 | }
40 |
41 | [TestMethod]
42 | public void Add_ThreeStrings()
43 | {
44 | byte[] array = null;
45 | array = array.Add(TEXT_DATA, TEXT_DATA, TEXT_DATA);
46 | ShouldEqualWithDiff(array.ToUTF8(), $"{TEXT_DATA}{TEXT_DATA}{TEXT_DATA}");
47 | }
48 |
49 | [TestMethod]
50 | public void Add_FourByteArrays()
51 | {
52 | byte[] array = LF.Add(HT, CR, FF);
53 | ShouldEqualWithDiff(array.ToUTF8(), $"{LF.ToUTF8()}{HT.ToUTF8()}{CR.ToUTF8()}{FF.ToUTF8()}");
54 | }
55 |
56 | [TestMethod]
57 | public void Add_ThreeByteArraysAndTwoStrings()
58 | {
59 | byte[] array = LF.Add(TEXT_DATA, CR, TEXT_DATA, FF);
60 | ShouldEqualWithDiff(array.ToUTF8(), $"{LF.ToUTF8()}{TEXT_DATA}{CR.ToUTF8()}{TEXT_DATA}{FF.ToUTF8()}");
61 | }
62 |
63 | [TestMethod]
64 | public void Add_IgnoringNonStringNonByteArray()
65 | {
66 | byte[] array = LF.Add(TEXT_DATA, 1, CR, DateTime.Now, 10.5, 785m, new UnitTest(), FF);
67 | ShouldEqualWithDiff(array.ToUTF8(), $"{LF.ToUTF8()}{TEXT_DATA}{CR.ToUTF8()}{FF.ToUTF8()}");
68 | }
69 |
70 | [TestMethod]
71 | public void QRCode_NoParameter()
72 | {
73 | QRCode(TEXT_DATA).Print(TEST_FILE);
74 | ShouldEqualWithDiff("\u001D(k\u0004\01A1\0\u001D(k\u0003\01C\u0004\u001D(k\u0003\01E0\u001D(k:\01P0Data test with some special characters: $ñáãç*/@\"'^{}\u001D(k\u0003\01Q0", File.ReadAllText(TEST_FILE));
75 | }
76 |
77 | [TestMethod]
78 | public void QRCode_FullParameters()
79 | {
80 | QRCode(TEXT_DATA, QRCodeModel.Model2, QRCodeCorrection.Percent30, QRCodeSize.Large).Print(TEST_FILE);
81 | ShouldEqualWithDiff("\u001D(k\u0004\01A2\0\u001D(k\u0003\01C\u0005\u001D(k\u0003\01E3\u001D(k:\01P0Data test with some special characters: $ñáãç*/@\"'^{}\u001D(k\u0003\01Q0", File.ReadAllText(TEST_FILE));
82 | }
83 |
84 | [TestMethod]
85 | public void Barcode_EAN8()
86 | {
87 | Barcode(BarCodeType.EAN8, "90311017", 52).Print(TEST_FILE);
88 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001Dh4\u001dw\u0003\u001DkD\u000890311017");
89 | }
90 |
91 | [TestMethod]
92 | public void Barcode_EAN13()
93 | {
94 | Barcode(BarCodeType.EAN13, "9780201379624", 52).Print(TEST_FILE);
95 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001Dh4\u001dw\u0003\u001DkC\r9780201379624");
96 | }
97 |
98 | [TestMethod]
99 | public void Barcode_CODE128()
100 | {
101 | Barcode(BarCodeType.CODE128, "ABC1234", 52).Print(TEST_FILE);
102 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001Dh4\u001dw\u0003\u001DkI\u0009{BABC1234");
103 | }
104 |
105 | [TestMethod]
106 | public void Barcode_UPC_A()
107 | {
108 | Barcode(BarCodeType.UPC_A, "72527273070", 52).Print(TEST_FILE);
109 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001Dh4\u001dw\u0003\u001DkA\u000B72527273070");
110 | }
111 |
112 | [TestMethod]
113 | public void DoubleHeight()
114 | {
115 | SelectCharSizeHeight(CharSizeHeight.Double).Add(TEXT_DATA).Print(TEST_FILE);
116 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001D!\u0001Data test with some special characters: $ñáãç*/@\"'^{}");
117 | }
118 |
119 | [TestMethod]
120 | public void DoubleWidthAndHeight()
121 | {
122 | SelectCharSize(CharSizeWidth.Double, CharSizeHeight.Double).Add(TEXT_DATA).Print(TEST_FILE);
123 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001D!\u0011Data test with some special characters: $ñáãç*/@\"'^{}");
124 | }
125 |
126 | [TestMethod]
127 | public void DoubleWidth()
128 | {
129 | SelectCharSizeWidth(CharSizeWidth.Double).Add(TEXT_DATA).Print(TEST_FILE);
130 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001D!\u0010Data test with some special characters: $ñáãç*/@\"'^{}");
131 | }
132 |
133 | [TestMethod]
134 | public void AlignCenter()
135 | {
136 | SelectJustification(Justification.Center).Add(TEXT_DATA).Print(TEST_FILE);
137 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001Ba\u0001Data test with some special characters: $ñáãç*/@\"'^{}");
138 | }
139 |
140 | [TestMethod]
141 | public void AlignRight()
142 | {
143 | SelectJustification(Justification.Right).Add(TEXT_DATA).Print(TEST_FILE);
144 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001Ba\u0002Data test with some special characters: $ñáãç*/@\"'^{}");
145 | }
146 |
147 | [TestMethod]
148 | public void AlignLeft()
149 | {
150 | SelectJustification(Justification.Left).Add(TEXT_DATA).Print(TEST_FILE);
151 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "\u001Ba\0Data test with some special characters: $ñáãç*/@\"'^{}");
152 | }
153 |
154 | [TestMethod]
155 | public void PrinterSameLineLeftAndRightAlignedText()
156 | {
157 | var printer = new Printer();
158 | printer.Columns = 5;
159 | byte[] result;
160 |
161 | //Pr 99
162 | result = printer.SameLineLeftAndRightAlignedText("Product", "99");
163 | ShouldEqualWithDiff(result.ToUTF8(), "Pr 99\u001Ba\u0000");
164 |
165 | //P 999
166 | result = printer.SameLineLeftAndRightAlignedText("Product", "999");
167 | ShouldEqualWithDiff(result.ToUTF8(), "P 999\u001Ba\u0000");
168 |
169 | //Pro 99
170 | printer.Columns = 6;
171 | result = printer.SameLineLeftAndRightAlignedText("Product", "99");
172 | ShouldEqualWithDiff(result.ToUTF8(), "Pro 99\u001Ba\u0000");
173 |
174 | // 99
175 | printer.Columns = 3;
176 | result = printer.SameLineLeftAndRightAlignedText("Product", "99");
177 | ShouldEqualWithDiff(result.ToUTF8(), "\u001Ba\u000299\u001Ba\u0000");
178 |
179 | // 99
180 | printer.Columns = 2;
181 | result = printer.SameLineLeftAndRightAlignedText("Product", "99");
182 | ShouldEqualWithDiff(result.ToUTF8(), "\u001Ba\u000299\u001Ba\u0000");
183 |
184 | //Product 99
185 | printer.Columns = 10;
186 | result = printer.SameLineLeftAndRightAlignedText("Product", "99");
187 | ShouldEqualWithDiff(result.ToUTF8(), "Product 99\u001Ba\u0000");
188 |
189 | //Product 99
190 | printer.Columns = 15;
191 | result = printer.SameLineLeftAndRightAlignedText("Product", "99");
192 | ShouldEqualWithDiff(result.ToUTF8(), "Product 99\u001Ba\u0000");
193 |
194 |
195 | printer = new Printer { Encoding = Encoding.UTF8, Columns = 32, Address = TEST_FILE };
196 | printer.AddToCache(
197 | printer.HorizontalDoubleLine,
198 | LF,
199 | printer.SameLineLeftAndRightAlignedText("Product Name", "Price"),
200 | LF,
201 | printer.HorizontalLine,
202 | LF,
203 | printer.SameLineLeftAndRightAlignedText("Sample Product", "$10.99"),
204 | LF,
205 | printer.SameLineLeftAndRightAlignedText("Sample Product with a very long description", "$0.01"),
206 | LF,
207 | AlignToRight,
208 | "----------",
209 | LF,
210 | CharSizeDoubleHeight,
211 | "$11.00",
212 | CharSizeReset,
213 | LF,
214 | "----------",
215 | AlignToLeft,
216 | LF,
217 | printer.HorizontalDoubleLine
218 | );
219 | printer.Print();
220 | ShouldEqualWithDiff(File.ReadAllText(TEST_FILE), "================================\nProduct Name Price\u001ba\u0000\n--------------------------------\nSample Product $10.99\u001ba\u0000\nSample Product with a very $0.01\u001ba\u0000\n\u001ba\u0002----------\n\u001d!\u0001$11.00\u001d!\u0000\n----------\u001ba\u0000\n================================");
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/ESCPOS/ESCPOSCommands/Commands.cs:
--------------------------------------------------------------------------------
1 | using ESCPOS.Utils;
2 | using System;
3 | using System.IO;
4 | using System.Net.Sockets;
5 | using System.Text;
6 |
7 | namespace ESCPOS
8 | {
9 | public static partial class Commands
10 | {
11 | ///
12 | /// ESC ! n
13 | ///
14 | public static byte[] SelectPrintMode(PrintMode printMode)
15 | => new byte[] { 0x1B, 0x21, (byte)printMode };
16 |
17 | ///
18 | /// ESC - n
19 | ///
20 | public static byte[] SelectUnderlineMode(UnderlineMode underlineMode)
21 | => new byte[] { 0x1B, 0x2D, (byte)underlineMode };
22 |
23 | ///
24 | /// ESC 2 / ESC 3 n
25 | ///
26 | public static byte[] SelectLineSpacing(LineSpacing lineSpacing)
27 | => lineSpacing == LineSpacing.Default ? new byte[] { 0x1B, (byte)lineSpacing } : new byte[] { 0x1B, 0x33, (byte)lineSpacing };
28 |
29 | ///
30 | /// ESC G n
31 | ///
32 | public static byte[] DoubleStrike(OnOff @switch)
33 | => new byte[] { 0x1B, 0x47, (byte)@switch };
34 |
35 | ///
36 | /// ESC M n
37 | ///
38 | public static byte[] SelectCharacterFont(Font font)
39 | => new byte[] { 0x1B, 0x4D, (byte)font };
40 |
41 | ///
42 | /// ESC R n
43 | ///
44 | public static byte[] SelectInternationalCharacterSet(CharSet charSet)
45 | => new byte[] { 0x1B, 0x52, (byte)charSet };
46 |
47 | ///
48 | /// ESC T n
49 | ///
50 | public static byte[] SelectPrintDirection(Direction direction)
51 | => new byte[] { 0x1B, 0x54, (byte)direction };
52 |
53 | ///
54 | /// ESC V n
55 | ///
56 | public static byte[] Turn90Degrees(ClockwiseDirection clockwiseDirection)
57 | => new byte[] { 0x1B, 0x56, (byte)clockwiseDirection };
58 |
59 | ///
60 | /// ESC a n
61 | ///
62 | public static byte[] SelectJustification(Justification justification)
63 | => new byte[] { 0x1B, 0x61, (byte)justification };
64 |
65 | ///
66 | /// ESC t n
67 | ///
68 | public static byte[] SelectCodeTable(CodeTable codeTable)
69 | => new byte[] { 0x1B, 0x74, (byte)codeTable };
70 |
71 | ///
72 | /// ESC { n
73 | ///
74 | public static byte[] UpsideDown(OnOff @switch)
75 | => new byte[] { 0x1B, 0x7B, (byte)@switch };
76 |
77 | ///
78 | /// GS ! n
79 | ///
80 | public static byte[] SelectCharSize(CharSizeWidth charSizeWidth, CharSizeHeight charSizeHeight)
81 | {
82 | var charSize = (byte)charSizeWidth | (byte)charSizeHeight;
83 | return new byte[] { 0x1D, 0x21, (byte)charSize };
84 | }
85 |
86 | ///
87 | /// GS ! n
88 | ///
89 | public static byte[] SelectCharSizeHeight(CharSizeHeight charSize)
90 | => new byte[] { 0x1D, 0x21, (byte)charSize };
91 |
92 | ///
93 | /// GS ! n
94 | ///
95 | public static byte[] SelectCharSizeWidth(CharSizeWidth charSize)
96 | => new byte[] { 0x1D, 0x21, (byte)charSize };
97 |
98 | ///
99 | /// GS H n
100 | ///
101 | public static byte[] SelectHRIPosition(HRIPosition hriPosition)
102 | => new byte[] { 0x1D, 0x21, (byte)hriPosition };
103 |
104 | ///
105 | /// GS k m n
106 | ///
107 | /// is .
108 | [Obsolete(nameof(PrintBarCode) + " is deprecated, please use " + nameof(Barcode) + " method instead.", true)]
109 | public static byte[] PrintBarCode(BarCodeType barcodeType, string barcode, int heightInDots = 162, BarcodeWidth barcodeWidth = BarcodeWidth.Normal)
110 | => Barcode(barcodeType, barcode, heightInDots, barcodeWidth);
111 |
112 | ///
113 | /// GS k m n
114 | ///
115 | /// is .
116 | public static byte[] Barcode(BarCodeType barcodeType, string barcode, int heightInDots = 162, BarcodeWidth barcodeWidth = BarcodeWidth.Normal, Encoding encoding = null)
117 | {
118 | var height = new byte[] { 0x1D, 0x68, (byte)heightInDots };
119 | var width = new byte[] { 0x1D, 0x77, (byte)barcodeWidth };
120 | var length = barcode.Length;
121 | var bar = (encoding ?? Encoding.UTF8).GetBytes(barcode);
122 | if (barcodeType == BarCodeType.CODE128)
123 | {
124 | length += 2;
125 | bar = new byte[] { 0x7B, 0x42 }.Add(bar); //Subset CODE B is selected for CODE128 bars
126 | }
127 | var settings = new byte[] { 0x1D, 0x6B, (byte)barcodeType, (byte)length };
128 |
129 | return height.Add(width, settings, bar);
130 | }
131 |
132 | public static byte[] ToBarcode(this string barcode, BarCodeType barCodeType, int heightInDots = 162, BarcodeWidth barcodeWidth = BarcodeWidth.Normal, Encoding encoding = null)
133 | => Barcode(barCodeType, barcode, heightInDots, barcodeWidth, encoding);
134 |
135 | ///
136 | /// GS ( k pL pH cn fn n1 n2
137 | ///
138 | /// is .
139 | [Obsolete(nameof(PrintQRCode) + " is deprecated, please use " + nameof(QRCode) + " method instead.", true)]
140 | public static byte[] PrintQRCode(string content, QRCodeModel qrCodeModel = QRCodeModel.Model1, QRCodeCorrection qrCodeCorrection = QRCodeCorrection.Percent7, QRCodeSize qrCodeSize = QRCodeSize.Normal)
141 | => QRCode(content, qrCodeModel, qrCodeCorrection, qrCodeSize);
142 |
143 |
144 | ///
145 | /// GS ( k pL pH cn fn n1 n2
146 | ///
147 | /// is .
148 | public static byte[] QRCode(string content, QRCodeModel qrCodeModel = QRCodeModel.Model1, QRCodeCorrection qrCodeCorrection = QRCodeCorrection.Percent7, QRCodeSize qrCodeSize = QRCodeSize.Normal, Encoding encoding = null)
149 | {
150 | var model = new byte[] { 0x1D, 0x28, 0x6B, 0x04, 0x00, 0x31, 0x41, (byte)qrCodeModel, 0x00 };
151 | var size = new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x43, (byte)qrCodeSize };
152 | var errorCorrection = new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x45, (byte)qrCodeCorrection };
153 | int num = content.Length + 3;
154 | int pL = num % 256;
155 | int pH = num / 256;
156 | var storeData = new byte[] { 0x1D, 0x28, 0x6B, (byte)pL, (byte)pH, 0x31, 0x50, 0x30 };
157 | var data = (encoding ?? Encoding.UTF8).GetBytes(content);
158 | var print = new byte[] { 0x1D, 0x28, 0x6B, 0x03, 0x00, 0x31, 0x51, 0x30 };
159 | return model.Add(size, errorCorrection, storeData, data, print);
160 | }
161 |
162 | public static byte[] ToQRCode(this string content, QRCodeModel qrCodeModel = QRCodeModel.Model1, QRCodeCorrection qrCodeCorrection = QRCodeCorrection.Percent7, QRCodeSize qrCodeSize = QRCodeSize.Normal, Encoding encoding = null)
163 | => QRCode(content, qrCodeModel, qrCodeCorrection, qrCodeSize, encoding);
164 |
165 |
166 | /// is empty, or in an unexpected format.
167 | /// is .
168 | public static void Print(this byte[] data, string printerAddress)
169 | {
170 | if (printerAddress == null)
171 | throw new ArgumentNullException(nameof(printerAddress));
172 |
173 | if (printerAddress.Length == 0)
174 | throw new ArgumentException("Printer address can't be empty", nameof(printerAddress));
175 |
176 | string[] splittedAddress = printerAddress.Split(':');
177 |
178 | //if printerAddress is a directly connected printer without sharing address, it will be accepted as "{host}:{port}" format
179 | if (splittedAddress.Length == 2)
180 | {
181 | string host = splittedAddress[0];
182 | string port = splittedAddress[1];
183 | if (!int.TryParse(port, out var portNumber))
184 | throw new ArgumentException($"Print address format should be {{host}}:{{port}}, but instead it is {host}:{portNumber}");
185 |
186 | using (Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
187 | {
188 | clientSocket.NoDelay = true;
189 | clientSocket.Connect(splittedAddress[0], portNumber);
190 | clientSocket.Send(data);
191 | }
192 | return;
193 | }
194 |
195 | string tempFile = "esc_pos.temp";
196 | if (File.Exists(tempFile))
197 | {
198 | try
199 | {
200 | File.Delete(tempFile);
201 | }
202 | catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
203 | {
204 | tempFile = $"{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{tempFile}";
205 | }
206 | }
207 |
208 | File.WriteAllBytes(tempFile, data);
209 | File.Copy(tempFile, printerAddress, true);
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/dotnet/sourcelink)
2 | [](https://dev.azure.com/igorocampos/PersonalProjects/_build?definitionId=1&_a=summary)
3 | [](#)
4 | [](http://www.nuget.org/packages/ESCPOS)
5 | [](ESCPOS/LICENSE)
6 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Figorocampos%2FESCPOS?ref=badge_shield)
7 |
8 | # ESCPOS
9 | A ESC/POS Printer Commands Helper.
10 |
11 | 
12 |
13 | # Installing via NuGet Package
14 |
15 | The NuGet Package can be found [here](https://www.nuget.org/packages/ESCPOS/) and you can install it with:
16 |
17 | ```powershell
18 | PM> Install-Package ESCPOS
19 | ```
20 |
21 | # Usage
22 | All command methods will return a **byte array** that you should concatenate with the bytes of your data, and then send it all to your printer using the `Print` extension method, which will send a byte array to the informed printer address. It can be something like `COM3`, `LPT1`, `\\127.0.0.1\printer`, `192.168.0.100:9100`, etc. or even a path to a text file like `./print.txt`.
23 |
24 | There is also 2 extension methods, `Add` and `ToBytes`, located in the namespace `ESCPOS.Utils`.
25 | The first one can be used in byte arrays, so you can concatenate 2 or more byte arrays just like this:
26 | ```cs
27 | byte[] result = array1.Add(array2, array3, ..., arrayN);
28 | ```
29 | In addition there's an overload to the mentioned `Add` method that will accept string parameters instead of byte arrays. It appends all strings into one new string and then converts it to a byte array.
30 | And yet another overload to accept the mix of byte arrays and strings parameters, but since it's accepting an object type parameter, this will ignore any parameter that is not `string` or `byte[]` (e.g. `int`).
31 |
32 | With `ToBytes` method you can convert a UTF-8 string to a byte array:
33 | ```cs
34 | byte[] result = "Some string".ToBytes();
35 | ```
36 |
37 | Alternatively you can choose whatever Encoding you wish to use for that:
38 | ```cs
39 | byte[] result = "汉字".ToBytes(Encoding.GetEncoding("GBK"));
40 | ```
41 | *Just make sure your Printer has a corresponding CodePage for that Encoding!
42 |
43 | ## Printer Class
44 | As an alternative, if you'd like to instantiate a Printer class that will handle all about bytes, encoding, and even have printer specific features like an horizontal line or print text aligned to the left AND to the right in the same line, which is only possible by knowing the number of columns your printer has, you can use the `Printer` class and its methods.
45 |
46 | ```cs
47 | //Instantiate printer with its details for encoding, column count and address
48 | var printer = new Printer { Encoding = Encoding.UTF8, Columns = 32, Address = "COM4" };
49 |
50 | //Add all bytes to the Cache
51 | printer.AddToCache(
52 | printer.HorizontalLine,
53 | LF,
54 | printer.SameLineLeftAndRightAlignedText("Sample Product", "$10.99"),
55 | LF,
56 | printer.HorizontalLine
57 | );
58 |
59 | //Send everything that is currently in the cache to the printer
60 | printer.Print();
61 | ```
62 |
63 | ## Examples
64 |
65 | All examples will assume the using statements below:
66 | ```cs
67 | using static ESCPOS.Commands;
68 | using ESCPOS;
69 | using ESCPOS.Utils;
70 | ```
71 |
72 | ### QRCode
73 | ```cs
74 | byte[] qrCodeCommand = QRCode("Some data");
75 | qrCodeCommand.Print("COM2");
76 | ```
77 |
78 | Or using the Extension Method
79 |
80 | ```cs
81 | string data = "Some data";
82 | data.ToQRCode().Print("COM2");
83 | ```
84 |
85 |
86 | ### Barcode
87 | ```cs
88 | byte[] barCodeCommand = Barcode(BarCodeType.EAN13, "9780201379624");
89 | barCodeCommand.Print("192.168.0.100:9100");
90 | ```
91 |
92 | Or using the Extension Method
93 |
94 | ```cs
95 | string code = "9780201379624";
96 | code.ToBarcode(BarCodeType.EAN13).Print("192.168.0.100:9100");
97 | ```
98 |
99 |
100 | ### Formatted Text
101 | ```cs
102 | byte[] cmd = AlignToCenter.Add(CharSizeDoubleHeight, "Fancy Title");
103 | cmd.Print(@"\\127.0.0.1\printer");
104 | ```
105 | ### Example using Printer class
106 | ```cs
107 | var printer = new Printer { Encoding = Encoding.UTF8, Columns = 32, Address = "192.168.0.100:9100" };
108 | printer.AddToCache(
109 | printer.HorizontalDoubleLine,
110 | LF,
111 | printer.SameLineLeftAndRightAlignedText("Product Name", "Price"),
112 | LF,
113 | printer.HorizontalLine,
114 | LF,
115 | printer.SameLineLeftAndRightAlignedText("Sample Product", "$10.99"),
116 | LF,
117 | printer.SameLineLeftAndRightAlignedText("Sample Product with a very long description", "$0.01"),
118 | LF,
119 | AlignToRight,
120 | "----------",
121 | LF,
122 | CharSizeDoubleHeight,
123 | "$11.00",
124 | CharSizeReset,
125 | LF,
126 | "----------",
127 | AlignToLeft,
128 | LF,
129 | printer.HorizontalDoubleLine
130 | );
131 | printer.Print();
132 | ```
133 |
134 | ### Full CFe SAT Receipt without using Printer class
135 | This example will assume that the variable `cfe` is a deserialized object from the [CFe](https://portal.fazenda.sp.gov.br/servicos/sat) XML, and will print the receipt using its fields.
136 | Also this example will print a 32 columns receipt, which is ideal for 56mm paper roll.
137 | ```cs
138 | var line = "--------------------------------";
139 |
140 | byte[] array = LF;
141 | array = array.Add(CharSizeDoubleHeight, AlignToCenter);
142 |
143 | if (cfe.infCFe.emit.xFant != null)
144 | array.Add(cfe.infCFe.emit.xFant);
145 |
146 | array.Add(LF, CharSizeReset, cfe.infCFe.emit.xNome,
147 | LF, $"{cfe.infCFe.emit.enderEmit.xLgr},{cfe.infCFe.emit.enderEmit.nro} {cfe.infCFe.emit.enderEmit.xBairro} - {cfe.infCFe.emit.enderEmit.xMun} {cfe.infCFe.emit.enderEmit.CEP}",
148 | LF, $"CNPJ: {cfe.infCFe.emit.CNPJ}",
149 | LF, $"IE: {cfe.infCFe.emit.IE}",
150 | LF, line, CharSizeDoubleHeight, $"Extrato No. {cfe.infCFe.ide.nCFe}",
151 | LF, "CUPOM FISCAL ELETRONICO - SAT", CharSizeReset,
152 | LF, LF);
153 |
154 | if (!string.IsNullOrEmpty(cfe.infCFe.dest?.Item))
155 | array.Add(line, "CPF/CNPJ do Consumidor:", cfe.infCFe.dest.Item, LF);
156 |
157 | array.Add(line,
158 | "#|COD|DESC|QTD|UN|VL UNIT R$|(VL TRIB R$)*|VL ITEM R$", LF,
159 | line,
160 | AlignLeft);
161 |
162 | int i = 1;
163 | foreach (var det in cfe.infCFe.det)
164 | {
165 | string prod = $"{det.prod.cProd} {det.prod.xProd} {det.prod.qCom:0.0##} {det.prod.uCom} X {det.prod.vUnCom:0.00#} {((det.imposto?.vItem12741 ?? 0) == 0 ? "" : $"({det.imposto.vItem12741:f2})*")}";
166 | array.Add($" {i++:D3} ");
167 | while (prod.Length > 20)
168 | {
169 | var wrap = prod.Length >= 20 ? prod.Substring(0, 20) : prod;
170 | array.Add(wrap), LF, " ");
171 | prod = prod.Substring(20);
172 | }
173 | array.Add(prod.PadRight(20), det.prod.vProd.ToString("f2").PadLeft(6), LF);
174 | }
175 |
176 | array.Add(LF);
177 |
178 | if (cfe.infCFe.total.ICMSTot.vDesc > 0)
179 | array.Add($" Desconto R${cfe.infCFe.total.ICMSTot.vDesc.ToString("f2").PadLeft(19)}", LF);
180 |
181 | if (cfe.infCFe.total.ICMSTot.vOutro > 0)
182 | array.Add($" Acrescimo R${cfe.infCFe.total.ICMSTot.vOutro.ToString("f2").PadLeft(18)}", LF);
183 |
184 | array.Add(CharSizeDoubleHeight, $" TOTAL R${cfe.infCFe.total.vCFe.ToString("f2").PadLeft(22)}", LF,
185 | CharSizeReset, LF);
186 |
187 | foreach (var mp in cfe.infCFe.pgto.MP)
188 | {
189 | string description;
190 | switch (Convert.ToInt32(mp.cMP ?? "1"))
191 | {
192 | case 2:
193 | description = "Cheque";
194 | break;
195 | case 3:
196 | description = "Cartao de Credito";
197 | break;
198 | case 4:
199 | description = "Cartao de Debito";
200 | break;
201 | case 5:
202 | description = "Credito na Loja";
203 | break;
204 | case 10:
205 | description = "Vale Alimentacao";
206 | break;
207 | case 11:
208 | description = "Vale Refeicao";
209 | break;
210 | case 12:
211 | description = "Vale Presente";
212 | break;
213 | case 13:
214 | description = "Vale Combustivel";
215 | break;
216 | case 14:
217 | description = "Duplicata Mercantil";
218 | break;
219 | case 90:
220 | description = "Sem Pagamento";
221 | break;
222 | default:
223 | description = "Dinheiro";
224 | break;
225 | }
226 |
227 | array.Add($" {description.PadRight(18)}{mp.vMP.ToString("f2").PadLeft(12)}", LF);
228 | }
229 |
230 | String accessKey = cfe.infCFe.Id.Substring(3, 44);
231 | array.Add($" Troco{cfe.infCFe.pgto.vTroco.ToString("f2").PadLeft(25)}", LF);
232 |
233 | foreach (var obs in cfe.infCFe.infAdic.obsFisco)
234 | array.Add($" {obs.xCampo}-{obs.xTexto}", LF);
235 |
236 | array.Add(line, "OBSERVACOES DO CONTRIBUINTE", LF);
237 | foreach (var item in cfe.infCFe.infAdic.infCpl.Split(';'))
238 | array.Add(item, LF);
239 |
240 | array.Add(LF, line, CharSizeDoubleHeight, AlignTOCenter, $"SAT No. {cfe.infCFe.ide.nserieSAT}",
241 | LF, CharSizeReset, DateTime.ParseExact($"{cfe.infCFe.ide.dEmi} {cfe.infCFe.ide.hEmi}", "yyyyMMdd HHmmss", System.Globalization.CultureInfo.InvariantCulture).ToString("dd/MM/yyyy HH:mm:ss"),
242 | LF, LF, CharSizeDoubleHeight, accessKey,
243 | LF, LF, accessKey.Substring(0, 22).ToBarcode(BarCodeType.CODE128, 30), accessKey.Substring(22).ToBarcode(BarCodeType.CODE128, 30),
244 | LF, LF,
245 | $"{accessKey}|{cfe.infCFe.ide.dEmi}{cfe.infCFe.ide.hEmi}|{cfe.infCFe.total.vCFe}|{cfe.infCFe.dest?.Item ?? ""}|{cfe.infCFe.ide.assinaturaQRCODE}".ToQRCode(),
246 | CharSizeReset,
247 | LF, line, LF, LF, LF);
248 |
249 | array.Print(@"\\127.0.0.1\printer");
250 | ```
251 |
252 | # Considerations
253 | When printing CODE128 barcodes, it will use automatically subset B, which supports numbers, upper and lower case letters and some additional characters.
254 |
255 | You can see the changelog [here](CHANGELOG.md).
256 |
257 |
258 | ## License
259 | [](https://app.fossa.io/projects/git%2Bgithub.com%2Figorocampos%2FESCPOS?ref=badge_large)
260 |
261 |
--------------------------------------------------------------------------------